容器背后的秘密
了解 bean 的一生
4. InitializingBean和init-method
org.springframework.beans.factory.InitializingBean是容器内部广泛使用的一个对象生命周期标识接口 ,其定义如下:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
该接口定义很简单,其作用在于,在对象实例化过程调用过“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提供计算交易日所需要排除的休息日信息。下方代码给出了封装类的部分代码。
public class FXTradeDateCalculator {
public static final DateTimeFormatter FRONT_DATE_FORMATTER = DateTimeFormat.forPattern("yyyyMMdd");
private static final Set<LocalDate> holidaySet = new HashSet<LocalDate>();
private static final String holidayKey = "JPY";
private SqlMapClientTemplate sqlMapClientTemplate;
public FXTradeDateCalculator(SqlMapClientTemplate sqlMapClientTemplate) {
this.sqlMapClientTemplate = sqlMapClientTemplate;
}
public void setupHolidays() {
List holidays = getSystemHolidays();
if (!ListUtils.isEmpty(holidays)) {
for (int i = 0, size = holidays.size(); i < size; i++) {
String holiday = (String) holidays.get(i);
LocalDate date = FRONT_DATE_FORMATTER.parseDateTime(holiday).toLocalDate();
holidaySet.add(date);
}
}
LocalDateKitCalculatorsFactory.getDefaultInstance().registerHolidays(holidayKey, holidaySet);
}
public DateCalculator<LocalDate> getForwardDateCalculator() {
return LocalDateKitCalculatorsFactory
.getDefaultInstance()
.getDateCalculator(holidayKey, HolidayHandlerType.FORWARD);
}
public DateCalculator<LocalDate> getBackwardDateCalculator() {
return LocalDateKitCalculatorsFactory
.getDefaultInstance()
.getDateCalculator(holidayKey, HolidayHandlerType.BACKWARD);
}
public List getSystemHolidays() {
return getSqlMapClientTemplate().queryForList("CommonContext.holiday", null);
}
}为了保证getForwardDateCalculator()和getBackwardDateCalculator()方法返回的DateCalculator已经将休息日考虑进去,在这两个方法被调用之前,我们需要setupHolidays()首先被调用,以保证将休息日告知DateCalculator,使它能够在计算交易日的时候排除掉这些休息日的日期。
因此,我们需要在配置文件中完成类似下方代码所示的配置,以保证在对象可用之前,setupHolidays()方法会首先被调用。
<beans>
<bean id="tradeDateCalculator" class="FXTradeDateCalculator" init-method="setupHolidays">
<constructor-arg>
<ref bean="sqlMapClientTemplate" />
</constructor-arg>
</bean>
<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
...
</bean>
...
</beans>重点在第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处理资源释放的数据源注册配置。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="driverClassName">
<value>${jdbc.driver}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
...
</bean>
重点在第一行的destroy-method="close"。
不过,这些自定义的对象销毁逻辑,在对象实例初始化完成并注册了相关的回调方法之后,并不会马上执行。回调方法注册后,返回的对象实例即处于使用状态,只有该对象实例不再被使用的时候,才会执行相关的自定义销毁逻辑,此时通常也就是Spring容器关闭的时候。但Spring容器在关闭之前,不会聪明到自动调用这些回调方法。所以,需要我们告知容器,在哪个时间点来执行对象的自定义销毁方法。
对于BeanFactory容器来说。
我们需要在独立应用程序的主程序退出之前,或者其他被认为是合适的情况下(依照应用场景而定),如下方代码所示,调用ConfigurableBeanFactory提供的destroySingletons()方法销毁容器中管理的所有singleton类型的对象实例。
public class ApplicationLauncher {
public static void main(String[] args) {
BasicConfigurator.configure();
BeanFactory container = new XmlBeanFactory(new ClassPathResource("..."));
BusinessObject bean = (BusinessObject)container.getBean("...");
bean.doSth();
// 主要是下面这行
((ConfigurableListableBeanFactory)container).destroySingletons();
// 应用程序退出,容器关闭
}
}
如果不能在合适的时机调用destroySingletons(),那么所有实现了DisposableBean接口的对象实例或者声明明了destroy-method的bean定义对应的对象实例,它们的自定义对象销毁逻辑就形同虚设,因为根本就不会被执行!
对于 ApplicationContext 容器来说。 道理是一样的 AbstractApplicationContext 为我们提供了 registerShutdownHook() 方法,该方法底层使用标准的 Runtime 类的 addShutdownHook() 方式来调用相应 bean 对象的销毁逻辑,从而保证在 Java 虚拟机退出之前,这些 singtleton 类型的 bean 对象实例的自定义销毁逻辑会被执行。当然 AbstractApplicationContext 注册的 shutdownHook 不只是调用对象实例的自定义销毁逻辑,也包括 ApplicationContext 相关的事件发布等,下方代码演示了该方法的使用。
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();
// 应用程序退出,容器关闭
}
}同样的道理,在 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的世界。