SpringBean加载机制 - 注入Bean
两种IoC容器
- xml配置文件加载的容器;
- 通过注解加载的容器;
xml容器用如下方式获得:
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("beans.xml");
通过XML添加的所有组件只会都在该容器中,用注解容器是拿不到对应的实例对象。同理,用注解添加的组件也只会在注解容器中。
注解容器通过如下方式获得:
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(
MainConfig.class,
OtherConfig.class,
ImportConfig.class);
如果想精确的控制可以调用AnnotationConfigApplicationContext
的无参构造器,然后手动调用里面的方法;如下:
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
context.register(MainConfig.class);
context.register(MainConfig.class);
context.register(MainConfig.class);
context.refresh();
分步调用可以在register()
执行完后进行额外的处理在调用refresh()
。后面说到这些额外的处理。
注入组件的四种方式
- @Configuration + @Bean
导入的第三方包里面的组件。因为我们无法直接在三方代码上加@Component。 - @ComponentScans + @ComponentScan +@Component
直接在代码上添加如上注解,导入自己写的类。 - @Import 快速给容器中导入一个组件
a. @Import(WantImportClass.class);容器中自动注册这个组件,id默认是全类名
b. 协议ImportSelector:返回需要导入的组件的全类名数组;
c. 协议ImportBeanDefinitionRegistrar:手动注册bean到容器中 - 使用Spring提供的 FactoryBean(工厂Bean);
a. 默认获取到的是工厂bean调用getObject创建的对象
b. 要获取工厂Bean本身,我们需要给id前面加一个&colorFactoryBean
方式一 @Configuration @Bean
该方式主要用来导入三方的类作为Bean添加IoC容器中。因为我们自己的类通常是在代码上添加@Component
和@ComponentScan
直接把类注入到IoC容器中。使用时通常有几个Config类,用来描述Bean组件。注意,需要手动调用AnnotationConfigApplicationContext
的register
方法将配置类加载到IoC容器中,当然调用Context的有参构造器也可以。
@Configuration
@Configuration
继承自@Component
,说明用该注解的修饰类本身也会作为组件添加的IoC容器中。其作用范围是@Target({ElementType.TYPE})
,也就是不能修饰方法上,通常用于修饰类,并在该配置类中声明要加载的bean。使用如下:
@Configuration
public class MainConfig {
@Bean(value = "tom")
public Person person01 (){
return person02();
}
@Bean(value = "jack")
public Person person02 (){
return new Person("jack",20);
}}
注意,如果类上没有@Configuration
,Bean会议'lite Mode'的模式加载到IoC容器中。在lite Mode
中tom
和jack
是不同的实例对象。有@Configuration
注解,tom
和jack
则是同一个实例对象。
详见官方文档
@Bean
@Bean的作用范围是@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
,通常情况下用于修饰有对象返回值的方法。
@Bean常用三个属性:
- value或name;
使用:@Bean("person")。如果value为空,默认用方法名作为id。 - initMethod;
- destroyMethod;
initMethod
和destroyMethod
这两个方法是用来指定bean的初始化之后和销毁之前调用的方法。会在Bean的生命周期在讨论。
方式二 @ComponentScans + @ComponentScan +@Component
该方式主要是将自己代码中的类注入成bean组件。
@ComponentScans + @ComponentScan
扫描制定路径下的所有类,将带有@Component
注解的类注入成Bean组件。这两个注解的使用范围都是ElementType.TYPE
,通常都是加在类上,Spring建议同时加上@Configuration
注解使用。示例:
//告诉Spring这是一个配置类
@Configuration
@ComponentScans(
value = {
@ComponentScan(value={
"com.springDemo.Bean",
"com.springDemo.Controller"}
includeFilters = {
@Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
@Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
},
useDefaultFilters = false)
}
)
public class MainConfig {
// other bean ...
}
@ComponentScans
的value是一个@Component
的数组,用以描述多个组件扫描的规则。
@ComponentScan
主要有以下四个属性:
- value : 指定要扫描的包,值是String数组;
- excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件;
- includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件;
- useDefaultFilters
默认只指定value的值,会将指定包中所有带@Component
的类注入成Bean。如果不想用默认的扫描规则需要useDefaultFilters = false
。同时指定excludeFilters
或includeFilters
。
@Filter
过滤规则使用@Filter
注解。该注解用以详细描述过滤的规则,有以下5个值。常用的是FilterType.CUSTOM
。这里不对过滤规则多详细介绍。
- FilterType.ANNOTATION:按照注解
- FilterType.ASSIGNABLE_TYPE:按照给定的类型;
- FilterType.ASPECTJ:使用ASPECTJ表达式
- FilterType.REGEX:使用正则指定
- FilterType.CUSTOM:使用自定义规则
@Component
该注解的范围是ElementType.TYPE
,通常只作用在类上。使用如下:
// @Component("customAnimalName")
@Component
public class Animal {
// ...
}
通常不需要指定Value的值,bean的id默认是类名。value值,用以改变bean id。
方式三 @Import
使用Import管理配置类
假设有N个配置类,我们可以通过在某一个配置类中用@Import
其他所有配置类,这样在AnnotationConfigApplicationContext
注册时就只要注册一个配置类即可,不需要记住那么多配置类。
使用如下:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
使用Import直接导入类作为Bean
使用如下:
//@Import导入组件,id默认是组件的全类名
@Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MainConfig {}
这种方式不太建议,因为直接用@Import
注册组件很难对组件做自定义设置,比如初始化方法,生命周期的管理,是单例还是多实例组件。
ImportSelector
实现接口ImportSelector
,在实现类中返回要添加Bean的全类名,就会把类注入到Ioc容器中。在selectImports
方法中可以做注入组件的逻辑判断,但是不能对组件的生命周期做业务处理。
示例:
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
//返回值,就是到导入到容器中的组件全类名
//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// TODO Auto-generated method stub
//importingClassMetadata
//方法不要返回null值
return new String[]{"com.spring.bean.Blue","com.spring.bean.Yellow"};
}
}
// 导入自定义组件
@Configuration
@Import({MyImportSelector.class})
public class MainConfig {
// ...
}
ImportBeanDefinitionRegistrar
实现接口ImportBeanDefinitionRegistrar
,在registerBeanDefinitions
方法中拿到registry
对象,调用registry.registerBeanDefinition()
方法将Bean对象注入到IoC容器中。这种方式比ImportSelector
方法更灵活一点,可以对Bean注入做更灵活的控制。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注册类;
* 把所有需要添加到容器中的bean;调用
* BeanDefinitionRegistry.registerBeanDefinition手工注册进来
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("com.spring.bean.Red");
boolean definition2 = registry.containsBeanDefinition("com.spring.bean.Blue");
if(definition && definition2){
//指定Bean定义信息;(Bean的类型,Bean。。。)
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个Bean,指定bean名
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
// 导入自定义组件
@Configuration
@Import({MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MainConfig {
// ...
}
注意虽然Import了MyImportBeanDefinitionRegistrar
和MyImportSelector
,但这两个类的Bean组件。
方式四 FactoryBean工厂注入Bean
如果一个Bean特别复杂,需要做很多处理,则可以用FactoryBean
来完成。该接口的实现类的不同方法共同定义了一个Bean对象。使用有两步:
- 实现
FactoryBean
; - 使用@Bean注入;
使用如下:
- 实现
FactoryBean
;
//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean {
//返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
// TODO Auto-generated method stub
System.out.println("ColorFactoryBean...getObject...");
return new Color();
}
@Override
public Class> getObjectType() {
// TODO Auto-generated method stub
return Color.class;
}
//是单例?
//true:这个bean是单实例,在容器中保存一份
//false:多实例,每次获取都会创建一个新的bean;
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return false;
}
}
- 注入Bean
@Configuration
public class MainConfig
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
}
这种方式即注入了Color Bean对象,同时也注入了ColorFactoryBean对象。两个Bean对象获取如下:
// 获取 Color Bean
Object bean1 = applicationContext.getBean("colorFactoryBean");
// 获取 ColorFactoryBean
Object bean2 = applicationContext.getBean("&colorFactoryBean");