容器背后的秘密
了解 bean 的一生
4. InitializingBean和init-method
org.springframework.beans.factory.InitializingBean
是容器内部广泛使用的一个对象生命周期标识接口 ,其定义如下:
该接口定义很简单,其作用在于,在对象实例化过程调用过“BeanPostProcessor
的前置处理”之后,会接着检测当前对象是否实现了InitializingBean
接口,如果是,则会调用其afterPropertiesSet()
方法进一步调整对象实例的状态。比如,在有些情况下,某个业务对象实例化完成后,还不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet()
中完成对该业务对象的后续处理。
虽然该接口在Spring容器内部广泛使用,但如果真的让我们的业务对象实现这个接口,则显得Spring容器比较具有侵入性。所以,Spring还提供了另一种方式来指定自定义的对象初始化操作 ,那就是在XML配置的时候,使用<bean>
的init-method
属性。
通过init-method
, 系统中业务对象的自定义初始化操作可以以任何方式命名,而不再受制于InitializingBean
的afterPropertiesSet()
。
如果系统开发过程中规定:所有业务对象的自定义初始化操作都必须以init()
命名,为了省去挨个<bean>
的设置init-method
这样的烦琐,我们还可以通过最顶层的 <beans>
的default-init-method
统一指定这一init()
方法名。
一般,我们是在集成第三方库,或者其他特殊的情况下,才会需要使用该特性。比如,ObjectLab
提供了一个外汇系统交易日计算的开源实现——ObjectLabKit
,系统在使用它提供的DateCalculator
时,封装类会通过一个自定义的初始化方法来为这些DateCalculator提供计算交易日所需要排除的休息日信息。下方代码给出了封装类的部分代码。
为了保证getForwardDateCalculator()
和getBackwardDateCalculator()
方法返回的DateCalculator
已经将休息日考虑进去,在这两个方法被调用之前,我们需要setupHolidays()
首先被调用,以保证将休息日告知DateCalculator
,使它能够在计算交易日的时候排除掉这些休息日的日期。
因此,我们需要在配置文件中完成类似下方代码所示的配置,以保证在对象可用之前,setupHolidays()
方法会首先被调用。
重点在第2行的init-method="setupHolidays
。
当然,我们也可以让FXTradeDateCalculator
实现InitializingBean
接口,然后将setupHolidays()
方法的逻辑转移到afterPropertiesSet()
方法。不过,相对来说还是采用init-method
的方式比较灵活,并且没有那么强的侵入性。
5. DisposableBean与destroy-method
当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean
接口。或者其对应的bean定义是否通过<bean>
的destroy-method
属性指定了自定义的对象销毁方法。如果是,就会为该实例注册一个用于对象销毁的回调(Callback
),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。
与InitializingBean
和init-method
用于对象的自定义初始化相对应,DisposableBean
和destroy-method
为对象提供了执行自定义销毁逻辑的机会。
最常见到的该功能的使用场景就是在Spring容器中注册数据库连接池,在系统退出后,连接池应该关闭,以释放相应资源。下方代码演示了通常情况下使zz用destroy-method
处理资源释放的数据源注册配置。
重点在第一行的destroy-method="close"
。
不过,这些自定义的对象销毁逻辑,在对象实例初始化完成并注册了相关的回调方法之后,并不会马上执行。回调方法注册后,返回的对象实例即处于使用状态,只有该对象实例不再被使用的时候,才会执行相关的自定义销毁逻辑,此时通常也就是Spring容器关闭的时候。但Spring容器在关闭之前,不会聪明到自动调用这些回调方法。所以,需要我们告知容器,在哪个时间点来执行对象的自定义销毁方法。
对于BeanFactory容器来说。
我们需要在独立应用程序的主程序退出之前,或者其他被认为是合适的情况下(依照应用场景而定),如下方代码所示,调用ConfigurableBeanFactory
提供的destroySingletons()
方法销毁容器中管理的所有singleton类型的对象实例。
如果不能在合适的时机调用destroySingletons()
,那么所有实现了DisposableBean
接口的对象实例或者声明明了destroy-method
的bean定义对应的对象实例,它们的自定义对象销毁逻辑就形同虚设,因为根本就不会被执行!
对于 ApplicationContext 容器来说。 道理是一样的 AbstractApplicationContext
为我们提供了 registerShutdownHook()
方法,该方法底层使用标准的 Runtime 类的 addShutdownHook()
方式来调用相应 bean 对象的销毁逻辑,从而保证在 Java 虚拟机退出之前,这些 singtleton 类型的 bean 对象实例的自定义销毁逻辑会被执行。当然 AbstractApplicationContext
注册的 shutdownHook
不只是调用对象实例的自定义销毁逻辑,也包括 ApplicationContext 相关的事件发布等,下方代码演示了该方法的使用。
同样的道理,在 Spring2.0引入了自定义 scope 之后, 使用自定义 scope 的相关对象实例的销毁逻辑,也应该在合适的时机被调用执行。不过,所有这些规则不包含 prototype 类型的 bean 实例,因为 prototype 对象实例在容器实例化并返回给请求方之后,容器就不再管理这种类型对象实例的生命周期了。 至此,bean走完了它在容器中“光荣”的一生。
本章小结
Spring的IoC容器主要有两种,即BeanFactory
和ApplicationContext
。本章伊始,首先对这两种容器做了总体上的介绍,然后转入本章的重点,也就是Spring的BeanFactory
基础容器。
我们从对比使用BeanFactory
开发前后的差别开始,阐述了BeanFactory
作为一个具体的IoC Service Provider
,它是如何 支持各种对象注册以及依赖关系绑定的。XML自始至终都是Spring的IoC容器支持最完善的ConfigurationMetadata
提供方式。所以,我们接着从XML入手,深入挖掘了BeanFactory
(以及ApplicationContext
)的各种潜力。
对于充满好奇心的我们,不会只停留在会使用BeanFactory
进行开发这一层面。所以,最后我们又一起探索了BeanFactory
(当然,也是ApplicationContext
)实现背后的各种奥秘。BeanFactory
是Spring提供的基础IoC容器,但并不是Spring提供的唯一IoC容器。ApplicationContext
构建于BeanFactory
之上,提供了许多BeanFactory
之外的特性。下一章,我们将一起走入ApplicationContext
的世界。