【Spring】IOC:实体Bean构建方式(xml、JavaConfig)及相关配置

在JAVA的世界中,一个对象A怎么才能调用对象B?通常有以下几种方法:

类别 描述 时间点
外部传入 构造方法传入
属性设置传入 设置对象状态时
运行时做为参数传入 调用时
内部创建 属性中直接创建 创建引用对象时
初始化方法创建 创建引用对象时
运行时动态创建 调用时

上表可以看到, 引用一个对象可以在不同地点(其它引用者)、不同时间由不同的方法完成。如果B只是一个非常简单的对象 如直接new B(),怎样都不会觉得复杂,比如你从来不会觉得创建一个String 是一个件复杂的事情。但如果B 是一个有着复杂依赖的Service对象,这时在不同时机引用B将会变得很复杂。

【Spring】IOC:实体Bean构建方式(xml、JavaConfig)及相关配置
无时无刻都要维护B的复杂依赖关系,试想B对象如果项目中有上百过,系统复杂度将会成陪数增加。IOC容器的出现正是为解决这一问题,其可以将对象的构建方式统一,并且自动维护对象的依赖关系,从而降低系统的实现成本。

IOC(Inversion of Control)控制反转:控制反转。就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们所看到的配置文件。

1.实体Bean的构建

1.1 xml 配置形式

xml 配置形式,顾名思义就是在单独的 xml 文件中对 bean 进行配置,常见的有以下几种形式:

1.ClassName反射构建

<!-- 默认构造函数构建 -->
<bean name="hello",class="com.my.spring.HelloSpring"></bean>

这是最常规的方法,其原理是在spring底层会基于 Class 属通过反射进行构建。

Object obj = HelloSpring.class.newInstance();

关于反射可以参考这篇 【JVM】第六篇:堆(Heap)上有什么?Class对象到反射

那我们该如何获取IOC容器中的Bean呢?

public static void main(String[] args)    
{    
     // 传入配置文件
     ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
     // getBean 可以传入id、name、Class 来获取唯一个 bean
     // 注:id与name的区别在于,id必须唯一,而name可以不唯一(冲突时后面的对象会覆盖前面的)
  	 System.out.println(ctx.getBean("hello"));  
}

2.指定构造方法构建

如果需要基于参数进行构建,就采用构造方法构建,其对应属性如下:

  • name:构造方法参数变量名称
  • type:参数类型(非必须)
  • index:参数索引,从0开始
  • value:参数值,spring 会自动转换成参数实际类型值
  • ref:引用容串的其它对象
public class HelloSpring {
	private String name;
	private int sex;
	
	public HelloSpring(){}
	
	public HelloSpring(String name) {
		this.name = name;
		this.sex = sex;
	}
}
<!-- 相当于是指定构造函数构建,而上面是用默认构造函数构建 -->
<bean class="com.my.spring.HelloSpring">
    <constructor-arg name="name" type="java.lang.String" value="zhangsan"/>
    <constructor-arg index="1" value="1" />
</bean>

3.静态工厂方法创建

该模式下必须创建一个静态工厂方法,并且方法返回该实例,spring 会调用该静态方法创建对象。

<!-- 指定Bean的类型,静态工厂方法(未特别指定则在当前类中) -->
<bean class="com.my.spring.HelloSpring" factory-method="build">
	<!-- 给工厂方法传入的参数名称,值。这里是要构造一个B类型的对象 -->
    <constructor-arg name="type" type="java.lang.String" value="B"/>
</bean>
// bulid 方法相当于一个静态工厂,返回已经创建好的对象
public static HelloSpring build(String type) {
	// 如果需要的是A类型的Bean
    if (type.equals("A")) {
        return new HelloSpring("luban",1);、
    // 如果需要的是B类型的Bean
    } else if (type.equals("B")) {
        return new HelloSpring("diaocan", 0);
    // 如果既不是A类型也不是B类型
    } else {
        throw new IllegalArgumentException("type must A or B");
    }
}

使用场景:如果你正在对一个对象进行A/B测试 ,就可以采用静态工厂方法的方式创建,其于策略创建不同的对像或填充不同的属性。

4.FactoryBean创建

指定一个Bean工厂来创建对象,对象构建初始化完全交给该工厂来实现。配置Bean时指定该工厂类的类名。

<!-- 构建一个工厂Bean  -->
<bean class="com.my.spring.MYFactoryBean"></bean>
// 实现FactoryBean接口
public class MYFactoryBean implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        return new HelloSpring();
    }
    @Override
    public Class<?> getObjectType() {
        return HelloSpring.class;
    }
    @Override
    public boolean isSingleton() {
        return false;
    }
}

FactoryBean:具有工厂生产对象能力的 bean,只能生成特定的对象。bean 必须实现 FactoryBean接口,此接口提供方法 getObject() 用于获得特定 bean。所以,使用时先创建FB实例,然后调用 getObject() 方法。

1.2 JavaConfig配置形式

JavaConfig 不再是单独的配置文件去配置,而是采用标了 @Configuration 注解的配置类去配置bean:

1.@Bean(适用于第三方组件)

  • 通过@Bean的形式是使用的话,bean的默认名称是方法名
  • 若@Bean(value=“bean的名称”) 那么bean的名称是指定的
@Configuration 
public class MainConfig { 
    @Bean     
    public Person person(){  
        return new Person(); 
    } 
}

如果是注解形式的,那我该如何获取这个bean呢?

public static void main(String[] args) { 
 // 传入配置类                                          
 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
 System.out.println(ctx.getBean("person"));  
}

2.@CompentScan(适用于自定义类)

在配置类上写@CompentScan注解来进行包扫描。除此之外还有 @Compent,@Controller,@Service,@Repository

@ComponetScan常用的属性字段有 basepackage(包)、excludeFilters(排除)、includeFilters(包含)。

注意,@CompentScan一般直接写在配置类上。

@Configuration 
// basePackages指定啊哟扫描的包
// 注意:如果要扫描的的包有多个,需要用{}包裹
@ComponentScan(basePackages = {"com.my.testcompentscan"}) 
public class MainConfig { 
}
@Configuration 
// excludeFilters 指定排除扫描的类
// 排除@Controller注解的,和TestService的)
@ComponentScan(basePackages = {"com.my.testcompentscan"}, excludeFilters = { 
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}), 
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {TestgService.class})
}) 
public class MainConfig { 
}
@Configuration 
// 只注入@Controller 和 @Service
// 注意:若使用包含的用法,需要把useDefaultFilters属性设置为false(true表示扫描全部的)
@ComponentScan(basePackages = {"com.my.testcompentscan"},includeFilters = { 
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, Service.class}) 
},useDefaultFilters = false) 
public class MainConfig { 
}

3.@Import(适用于特定场景)

定义在配置类上,value指定导入的类,可多个。也可以实现ImportSelector重写selectImports定义导入类的注解信息

@Configuration
@Import(value = {Person.class, Car.class})
public class MainConfig { 
}
public class MYImportSelector implements ImportSelector { 
    // 可以获取导入类的注解信息     
    @Override   
    public String[] selectImports(AnnotationMetadata importingClassMetadata) { 
    	return new String[]{"com.my.testimport.compent.Dog"};   
    } 
} 

@Configuration 
@Import(value = {Person.class, Car.class, MYImportSelector.class})
public class MainConfig { 
}

2.Bean相关配置

上面我们只是介绍了如何通过配置的形式声明一个bean,但是具体bean中可以配置哪些信息还不知道,下面我们就一起来看看。

2.1 作用域

类别 说明
singleton Spring IOC 容器中仅存在一份 bean 实例
prototype 每次都创建新的实例
request 一次 http 请求创建一个新的 bean,仅适用于 WebApplicationContext
session 一个 session 对应一个 bean 实例,仅适用于 WebApplicationContext
globalSession 一般用于 Portlet 应用环境,作用域仅适用于 WebApplicationContext 环境

三点注意:

  • 如果不特别指定作用域,那么就默认是singleton
  • singleton默认采用的是饿汉模式加载(容器启动实例就创建好了)
  • prototype默认采用的是懒汉模式加载(IOC容器启动的时候,并不会创建对象,而是 在第一次使用的时候才会创建)

xml:scope

<bean class="com.my.spring.HelloSpring" scope="prototype"></bean>

JavaConfig:@Scope

@Bean    
@Scope(value = "prototype")   
public Person person() {     
    return new Person();  
} 

2.2 生命周期(单例可控)

创建 -> 初始化 -> 销毁。由容器管理Bean的生命周期,我们可以自己指定bean的初始化方法和bean的销毁方法。

  • 针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法。
  • 针对多实例bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的时候被创建,而且bean的销毁不受 IOC容器的管理。

xml:init-method destroy-method

将这两个参数加载要注入的<bean>

<!-- 指定创建和销毁时执行的生命周期方法 -->
<bean class="com.my.spring.HelloSpring" init-method="onInit" destroy-method="onDestroy"></bean>
public class HelloSpring {
    public void onInit() {
        System.out.println("onInit");
    }
    public void onDestroy() {
        System.out.println("onDestroy");
    }
}

JavaConfig:@PostConstruct @PreDestory

将前置与后置方法定义在要加入IOC的Bean内

@Component 
public class Book { 
    
    public Book() {  
        System.out.println("book 的构造方法");   
    } 
    @PostConstruct   
    public void init() {   
        System.out.println("book 的PostConstruct标志的方法");  
    } 
    @PreDestroy   
    public void destory() {     
        System.out.println("book 的PreDestory标注的方法");   
    }
}

2.3 懒加载(单例)

主要针对单实例的bean 容器启动的时候,不创建对象,在第一次使用的时候才会创建该对象

  • 懒加载:容器启动的更快,

  • 非懒加载:可以容器启动时更快的发现程序当中的错误

选择哪一个就看追求的是启动速度,还是希望更早的发现错误,一般我们会选择后者。

xml:default-lazy-init

xml 要配置在最开始的标签内,里面所有的bean都要懒加载

  • true: 懒加载,即延迟加载
  • false(默认):非懒加载,容器启动时即创建对象
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd"
default-lazy-init="true">

JavaConfig:@Lazy

定义在JavaConfig类中,只指定当前bean

@Bean    
@Lazy     
public Person person() {  
    return new Person();  
} 
上一篇:JavaConfig的是实现


下一篇:什么是 JavaConfig?