引言
上一篇关于IoC容器的详解《Spring —— IoC 容器详解》真是工程浩大,可以说Spring官网对核心中的核心IOC容器做了非常全面的使用说明,包括在《Spring揭秘》中让我一直没有成功的Method Injection,官网也解决了我的疑惑,并最终实验成功(未来会另起一篇单独对“方法注入”做以总结)。
Spring官网的容器说明虽然全面,但是对于容器内部的处理并未深入解释,因此本篇博客做理论性的补充,总结自王富强老师的《Spring揭秘》第四章——“容器背后的秘密”。而且,本篇文章在工作和面试中更具有理论性的指导意义。
一、概述
Spring容器通过某种方式加载xml(或注解、JavaConfig)中的配置数据,然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。这个过程分为两个阶段,即容器启动阶段和Bean实例化阶段:
1、容器启动阶段(四步:加载、解析、组装BeanDefinition、注册):
容器刚开始启动时,首先加载配置,然后是解析并分析配置信息,将分析后的信息装配到相应的BeanDefinition,最后把将BeanDefinition注册到BeanDefinitionRegistry,启动完成。
总的来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集,一些验证性或辅助性的工作也可以在这个阶段完成。
2、Bean实例化阶段(四步:检查、实例化、装配、生命周期回调):
第一阶段后,所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过getBean()方法明确地请求某个对象,或因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段。
容器首先会检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口(Aware)的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。
二、容器启动阶段的扩展
Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。
该机制允许我们在容器实例化对象之前,对注册到容器中的BeanDefinition进行修改。相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改bean的某些属性,为bean定义增加其他信息等。
如果要自定义BeanFactoryPostProcessor,通常就需要实现该接口,因为一个容器可能拥有多个后处理器,因此可能需要同时实现Ordered接口(如果顺序确实必要)。因为Spring已经提供了几个现成的后处理器实现,因此大多数时候我们很少去实现某个后处理器。其中PropertyPlaceholderConfigurer和PropertyOverrideConfigurer是两个比较常用的BeanFactoryPostProcessor。
1、对于BeanFactory需要手动装配BeanFactoryPostProcessor。
2、对于ApplicationContext,可以自动识别配置中的BeanFactoryPostProcessor。xml配置形式如下:
<bean class=”...PropertyPlaceholderConfigurer”>
// 后处理器的一些属性
</bean>
三、bean的生命周期
容器在启动之后,并不会马上就实例化相应的bean定义。刚刚启动的容器仅仅拥有所有对象的BeanDefinition来保存实例化阶段将会用到的必要信息。
BeanFactory的getBean方法可以被客户端对象显式调用,也可以在容器内隐式调用。
隐式调用有以下两种情况:
1、对于BeanFactory来说,对象实例化默认采用延迟初始化。A依赖B,如果当程序请求A对象时,容器会检测A依赖的B是否已经实例化,如果没有会隐式调用getBean实例化B对象,这对于本次请求者是隐式的。
2、ApplicationContext启动之后会实例化所有的bean定义,在ApplicationContext的实现过程中,依然遵循Spring容器的两个阶段,只不过它会在启动阶段完成后,立刻调用所有bean定义的实例化方法getBean(这就是为什么当你得到ApplicationContext类型的容器引用时,容器内所有对象已经被全部实例化完成)。
(提示:AbstractBeanFactory类的getBean()方法的完整实现逻辑,和AbstractAutowiredCapableBeanFactory类的createBean()方法的全貌)
3.1 Bean的实例化策略与BeanWrapper
容器在内部实现的时候,采用“策略模式”来决定采用何种方式实例化bean。
通常可以通过反射或CGLIB动态字节码生成来实例化相应的bean实例或动态生成其子类。
InstantiationStrategy接口定义了bean的实例化策略。
SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象,但不支持方法注入方式的对象实例化。
CglibSubclassingInstantiationStrategy扩展了SimpleInstantiationStrategy,加入了CGLIB的动态字节码生成功能,可以动态的生成某个类的子类,满足了方法注入所需的对象实例化需求。这是容器默认采用的实例化策略。
容器只要根据相应的bean的BeanDefinition,和
CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的bean进行了包裹,返回相应的BeanWrapper实例。到这里,第一步实例化结束。
BeanWrapper接口通常在Spring框架内部使用,它有一个实现类:BeanWrapperImpl,其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。第一步结束后返回BeanWrapper实例而不是原先的对象实例,其目的就是为了第二步的“设置对象属性”。
BeanWrapper继承了PropertyAccessor接口,可以以统一的方式对对象属性进行访问,同时又继承了PropertyEditorRegistry和TypeConverter接口(间接)。当把各种PropertyEditor注册给容器时,后面就会被BeanWrapper用到。
在第一步构造完成对象之后,Spring会根据对象实例构造一个BeanWrapperImpl实例,然后将之前CustomEditorConfigurer注册的PropertyEditor复制一份给BeanWrapperImpl实例,这就是为什么BeanWrapper同时也是PropertyEditorRegistry,这样,BeanWrapper就可以完成类型转换、设置对象属性值等操作了。
以下是两段分别通过BeanWrapper和Java反射API来设置对象属性值和获取属性值的代码片段,相比于Java反射API,Spring提供的BeanWrapper操作起来更加流程简洁:
BeanWrapper方式:
Object provider = Class.forName(“package.name.FXNewsProvider”).newInstance();
Object listener = Class.forName(“...DowJonesNewsListener”).newInstance();
Object persister = Class.forName(“...DowJonesNewsPersister”).newInstance();
BeanWrapper newsProvider = new BeanWrapperImpl(provide);
newsProvider.setPropertyValue(“newsListener”, listener);
newsProvider.setPropertyValue(“newsPersister”, persister);
assertTrue(newsProvider.getWrapperedInstance() instanceof FXNewsProvider);
assertSame(provider, newsProvider.getWrapperedInstance());
assertSame(listener, newsProvider.getPropertyValue(“newsListener”));
Java反射API方式:
Object provider = Class.forName(“package.name.FXNewsProvider”).newInstance();
Object listener = Class.forName(“...DowJonesNewsListener”).newInstance();
Object persister = Class.forName(“...DowJonesNewsPersister”).newInstance();
Class providerClazz= provider.getClass();
Field listenerField = providerClazz.getField(“newsListener”);
listenerField.set(provider , listener);// 只演示listener属性设置,persister类似
assertSame(listener, listenerField.get(provider));
可以看出,Java反射API的方式在使用上相对混乱,且不便于记忆,而且还有紧随其后的各种异常需要处理(上面并未写出)。
3.2 Aware生命周期回调
当对象实例化完成并且相关属性以及依赖设置完成后,spring 容器会检查当前对象是否实现了一系列以Aware结尾的接口。如果是,则将Aware接口中规定的依赖注入给当前实例。
常见的Aware接口有:
1、BeanNameAware,容器会将bean定义对应的beanName设置到当前对象的实例。
2、BeanClassLoaderAware,容器会将对应加载当前bean的Classloader注入到当前对象实例。默认会使用加载springframework..ClassUtils类的Classloader。
3、BeanFactoryAware,BeanFactory容器会将自身设置到当前对象实例。当前对象就拥有了一个BeanFactory容器的引用,并且可以对这个容器内允许访问的对象按照需要进行访问。
这三个Aware接口只针对BeanFactory类型的容器而言,对于ApplicationContext类型的容器,也存在几个Aware相关接口。
ApplicationContext检测以下这些Aware接口并设置相关依赖的实现方式是通过BeanPostProcessor,这与前面的三个有所不同。不过设置Aware接口与BeanPostProcessor是相邻的,也可以放在一起讨论
1、ResourceLoaderAware,ApplicationContext实现了Spring的ResourceLoader接口,当容器检测到对象实现了ResourceLoaderAware接口后,会将当前ApplicationContext自身设置到对象中,这样当前对象就拥有了其所在ApplicationContext的一个引用。
2、ApplicationEventPublisherAware,ApplicationContext同样实现了ApplicationEventPublisher接口,这样,它就可以作为ApplicationEventPublisher来使用,所以,如果对象实现了ApplicationEventPublisherAware接口,容器就同样会将自身注入当前对象。
3、ApplicationContextAware,和前面一样,容器会将自身注入当前对象。
4、MessageSourceAware,和前面一样,容器会将自身注入当前对象。
3.3 BeanPostProcessor
如何与BeanFactoryPostProcessor区分?
BeanPostProcessor存在于对象实例化阶段,BeanFactoryPostProcessor存在于容器启动阶段。BeanFactoryPostProcessor会处理容器内所有符合条件的BeanDefinition,BeanPostProcessor会处理容器内所有符合条件的实例化后的对象实例。它包含两个接口方法:
postProcessBeforeInitialization(Object bean, String beanName);
postProcessAfterInitialization(Object bean, String beanName);
常见的使用场景是处理标记接口实现类,或者为当前对象提供代理实现。
ApplicationContext对应的那些Aware接口实际上就是通过BeanPostProcessor的方式进行处理的。当ApplicationContext中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时,容器会检测到之前注册到容器的ApplicationContextAwareProcessor这个BeanPostProcessor的实现类,然后调用其postProcessBeforeInitialization方法,检查并设置Aware相关依赖:
if (bean instanceof EnvironmentAware) {((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
3.4 自定义BeanPostProcessor
首先需要明确,BeanPostProcessor处理的是bean实例化阶段的一个过程,因此需要处理的对象必须是bean,即必须注册到ApplicationContext容器中,才能够被该接口实现类处理。
第一步:明确需要执行的操作,比如需要提前为目标bean的某个属性做转化,或赋值。然后定义该功能的标记接口,提供可以被BeanPostProcessor访问的接口方法。
第二步:让目标bean实现标记接口。
第三步:实现BeanPostProcessor接口,并注册到容器。通过instanceof关键字判断目标bean是否为标记接口的子类,如果是,则进行相关处理逻辑,最后返回bean。
注意,标记接口和BeanPostProcessor接口本身不存在直接的继承或依赖关系,另外,标记接口实际上并不是必须的,这是为了更好的限定处理逻辑的操作范围而存在的,也就是说,我们可以通过自定义的BeanPostProcessor子类,通过instanceof直接判断是否为目标bean类型的对象,然后直接处理,但这样可能会在BeanPostProcessor中暴露太多无关于处理逻辑的属性细节。标记接口很好的隔绝了BeanPostProcessor与目标bean类型之间的耦合。
3.5 InitializingBean和init-method
InitializingBean是容器内部广泛使用的一个对象生命周期标识接口。
它只有一个接口方法:afterPropertiesSet()。其作用是在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用afterPropertiesSet()方法进一步调整对象实例的状态。
比如,在某些情况下,某个业务对象实例化完成后,还不能处于可以使用的状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet()中完成对该业务对象的后续处理。
实现这个接口会让Spring框架带有侵入性,因此init-method属性可以替代“实现InitializingBean接口”完成初始化方法。另外,可以让bean直接定义名为init()的方法,Spring容器也会在这一阶段执行初始化逻辑。
一般,我们在集成第三方库,或者其他特殊的情况下,才会需要使用该特性。