IoC 容器元老-BeanFactory
0x01 IoC 容器服务提供者-BeanFactory
在开始之前,我们需要知道Spring IoC容器和IoC Service Provider之间的关系:IoC Service Provider是Spring IoC容器体系的一部分。
Spring的IoC容器和IoC Service Provider之间的关系
上面讲到的IoC Service Provider只是一个概念,只说了它需要做什么,但是具体谁来做,还是不确定的。
事实上,在spring家族中,承诺提供这项服务的主要有以下这两个对象:
BeanFactory和ApplicationContext继承关系
- BeanFactory
基础类型IoC容器,提供完整的IoC服务支持。默认采用延迟加载,也就是在需要的时候,才去召唤、创建对象。故在项目启动的时候可以非常快。缺点是在使用时,第一次召唤对象会比较慢。- ApplicationContext
ApplicationContext在BeanFactory的基础上构建,是相对比较高
级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。启动时需要大量资源,但在真正使用的时候,可以迅速将对象召唤出来。
BeanFactory承诺提供的服务如下:
其中主要就是召唤对象的方法-getBean。
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
有了BeanFactory提供的服务后,在真正战斗的时候,我们就可以直接调用getBean方法将对象召唤出来,然后完成自己的任务。
等等,还有一些疑问!究竟是谁将这些对象、神兽放到容器(卷轴)里面去的?是如何放进去的?九尾妖狐是鸣人他老爸封印进去的,而蛤蟆是鸣人自己与他们建立契约关系的。
也就是说,在真正召唤之前,总要先与被召唤这建立某种联系,最终才能够顺利召唤出来。
下面是召唤九尾妖狐的过程:
BeanFactory container = new XmlBeanFactory(new ClassPathResource("肚子-嘴遁"));
神兽 九尾妖狐 = container.getBean("九尾妖狐");
九尾被封印在鸣人的肚子里面,然后鸣人通过嘴遁的方式说服九尾出来帮忙。
回到BeanFactory,我们通常只需将“生产线图纸”交给BeanFactory,让
BeanFactory为我们生产就可以了。
“生产图纸”或者与对象之间建立联系的形式可以多种多样,例如:
- 可以通过直接编码的方式
- 可以通过xml的方式
- 可以通过注解的方式
- 通过文本的方式
直接编码的方式-代码片段
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
// 将bean定义注册到容器中
beanRegistry.registerBeanDefinition("djNewsProvider", newsProvider);
xml的方式
<beans>
<bean id="djNewsListener" class="..impl.DowJonesNewsListener">
</bean>
</beans>
注解方式
//创建对象
@Component
@Controller
@Service
//召唤对象
@Autowired
@Resource
0x02 Magic Happens Here
子曰:学而不思则罔。除了了解Spring的IoC容器如何使用,了解Spring的IoC容器都提供了哪些功能,我们也应该想一下,Spring的IoC容器内部到底是如何来实现这些的呢?
即将揭示的奥秘所在
Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean实例化阶段。
容器功能实现的各个阶段容器启动阶段
容器启动伊始,首先会通过某种途径加载Configuration MetaData。
XML配置信息到BeanDefinition的映射
总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然,一些验证性或者辅助性的工作也可以在这个阶段完成。
Bean实例化阶段
经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。
如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。
0x03 插手“容器启动”阶段
Spring的设计思想里面有很多值得学习的东西。例如在启动过程中,如果我们需要有一些业务需求,我们是可以参与其中的。
这里需要学习的地方:
会用spring留下的这些口子去做业务
会使用这种留后路的思想去设计代码
Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。
如果要自定义实现BeanFactoryPostProcessor,通常我们需要实现org.springframework.
beans.factory.config.BeanFactoryPostProcessor接口。同时,因为一个容器可能拥有多个Bean-FactoryPostProcessor,这个时候可能需要实现类同时实现Spring的org.springframework.core.Ordered接口,以保证各个BeanFactoryPostProcessor可以按照预先设定的顺序执行(如果顺序紧要的话)。
通常,Spring还会提供几个现成的扩展供你使用。下面让我们看一下Spring提供的这几个BeanFactoryPostProcessor实现都可以完成什么功能。
1. PropertyPlaceholderConfigurer
通常情况下,我们不想将类似于系统管理相关的信息同业务对象相关的配置信息混杂到XML配置文件中,以免部署或者维护期间因为改动繁杂的XML配置文件而出现问题。我们会将一些数据库连接信息、邮件服务器等相关信息单独配置到一个properties文件中,这样,如果因系统资源变动的话,只需要关注这些简单properties配置文件即可。
2. PropertyOverrideConfigurer
通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。做一些个性化处理。
下面是针对dataSource定义给出的PropertyOverrideConfigurer的propeties文件配置信息:
# pool-adjustment.properties
dataSource.minEvictableIdleTimeMillis=1000
dataSource.maxActive=50
当按照如下代码,将PropertyOverrideConfigurer加载到容器之后, dataSource原来定义的默认值就会被pool-adjustment.properties文件中的信息所覆盖:
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="pool-adjustment.properties"/>
</bean>
3. CustomEditorConfigurer
XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而CustomEditorConfigurer就是帮助我们传达类似信息的。例如你要对日期的格式做转换。
0x04 了解bean的一生
bean主要有两个阶段:容器启动阶段和bean实例化阶段。
我们借助于BeanFactoryPostProcessor来干预容器启动阶段。
容器通过调用getBean的方法,开启bean实例化的征程。
容器服务提供者有两个:BeanFactory和ApplicationContext。
Bean的实例化过程
- BeanFactory默认延迟加载,只有在第一次使用对象的时候,才调用getBean去初始化bean。
- ApplicationContext会在启动活动完成之后,主动调用getBean。这就是为什么当你得到ApplicationContext类型的容器引用时,容器内所有对象已经被全部实例化完成。
1. Bean的实例化与BeanWrapper
创建实例对象,设置实例属性
2. 各色的Aware接口
当对象实例化完成并且相关属性以及依赖设置完成之后,Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。常用的Aware接口如下:
org.springframework.context.ApplicationContextAware //容器
org.springframework.context.MessageSourceAware
org.springframework.context.ApplicationEventPublisherAware
org.springframework.context.ResourceLoaderAware
org.springframework.beans.factory.BeanFactoryAware
org.springframework.beans.factory.BeanClassLoaderAware
org.springframework.beans.factory.BeanNameAware
我们先来看看Aware接口的注入原理,如下(postProcessBeforeInitialization属于BeanPostProcessor的一个方法,见下文):
public Object postProcessBeforeInitialization(Object bean, String beanName) throws ➥
BeansException {
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);
}
return bean;
3. BeanPostProcessor
BeanPostProcessor的概念容易与BeanFactoryPostProcessor的概念混淆。但只要记住Bean-PostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段,这两个概念就比较容易区分了。与BeanFactoryPostProcessor通常会处理容器内所有符合条件的BeanDefinition类似,BeanPostProcessor会处理容器内所有符合条件的实例化后的对象实例。该接口声明了两个方法,分别在
两个不同的时机执行,见如下代码定义:
public interface BeanPostProcessor{
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
合理利用BeanPostProcessor这种Spring的容器扩展机制,将可以构造强大而灵活的应用系统。扩展的时候,我们可以实现BeanPostProcessor接口,然后将这个实现类给容器托管即可(可以通过xml配置文件或者@Compenent注解)
4. InitializingBean和init-method
org.springframework.beans.factory.InitializingBean是容器内部广泛使用的一个对象生命周期标识接口,其定义如下:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
该接口定义很简单,其作用在于,在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用其afterPropertiesSet()方法进一步调整对象实例的状态.
5. DisposableBean与destroy-method
当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean接口。或
者其对应的bean定义是否通过<bean>的destroy-method属性指定了自定义的对象销毁方法。如果是,就会为该实例注册一个用于对象销毁的回调(Callback),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。
对于ApplicationContext容器来说。道理是一样的。但AbstractApplicationContext为我们提供了registerShutdownHook()方法,该方法底层使用标准的Runtime类的addShutdownHook()方
式来调用相应bean对象的销毁逻辑,从而保证在Java虚拟机退出之前,这些singtleton类型的bean对象实例的自定义销毁逻辑会被执行。当然AbstractApplicationContext注册的shutdownHook不只是调用对象实例的自定义销毁逻辑,也包括ApplicationContext相关的事件发布等,代码清单4-58演示了该方法的使用。
public class ApplicationLauncher
{
public static void main(String[] args) {
BasicConfigurator.configure();
BeanFactory container = new ClassPathXmlApplicationContext("...");
((AbstractApplicationContext)container).registerShutdownHook();
BusinessObject bean = (BusinessObject)container.getBean("...");
bean.doSth();
// 应用程序退出,容器关闭
}
}
小结:
Spring的IoC容器主要有两种,即BeanFactory和ApplicationContext。阐述了BeanFactory作为一个具体的IoC Service Provider,它是如何支持各种对象注册以及依赖关系绑定的。BeanFactory是Spring提供的基础IoC容器,但并不是Spring提供的唯一IoC容器。ApplicationContext构建于BeanFactory之上,提供了许多BeanFactory之外的特性。