容器背后的秘密

了解 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系统中业务对象的自定义初始化操作可以以任何方式命名,而不再受制于InitializingBeanafterPropertiesSet()

如果系统开发过程中规定:所有业务对象的自定义初始化操作都必须以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类型的对象实例销毁之前,执行销毁逻辑。

InitializingBeaninit-method用于对象的自定义初始化相对应,DisposableBeandestroy-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容器主要有两种,即BeanFactoryApplicationContext。本章伊始,首先对这两种容器做了总体上的介绍,然后转入本章的重点,也就是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的世界。