1.6 自定义Bean

Spring提供了一系列的接口让你可以自定义Bean。

1.6.1 生命周期回调

要与容器中bean的生命周期进行交互,可以实现Spring的InitializingBeanDisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),以便bean在初始化和销毁时执行某些操作。

JSR-250 的@PostConstruct@PreDestroy注解是生命周期回调的最推荐的方式,因为他们和Spring框架解耦。

如果你不想使用JSR-250,又想解耦,那么可以考虑使用init-methoddestroy-method 这样两个定义bean的元数据。

在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口,并调用适当的方法。如果你需要自定义特性或其他Spring默认不提供的生命周期行为,你可以自己实现BeanPostProcessor。有关详细信息,请参见容器扩展点。

除了初始化和销毁回调之外,Spring管理的对象还可以实现生命周期接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。

生命周期回调接口在本节中进行了描述。

初始化回调

org.springframework.beans.factory.InitializingBean 接口可以让bean在Spring初始化之后,执行自定义的初始化流程,接口只有一个方法InitializingBean

void afterPropertiesSet() throws Exception;

但是我们不建议你使用InitializingBean,这个和Spring框架耦合在一起。我们推荐使用@PostConstruct或者定义一个POJO的初始化方法。如果使用XML配置,可以使用init-method来指定一个void返回值的方法。

如果使用Java配置,可以配置@BeaninitMethod属性,参照下面例子:

  • XML文件
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
  • Java
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}

上面例子和下面是等价的:

  • XML
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
  • Java
public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }
}

但是,最上面的例子和Spring是解耦的。

销毁回调

要实现销毁回调,可以实现接口org.springframework.beans.factory.DisposableBean, 他提供了一个销毁Bean方法:

void destroy() throws Exception;

我们不建议你使用DisposableBean,因为他和Spring框架耦合,我们推荐你使用@PreDestroy注解,或者一个能被bean定义支持的通用方法。

在XML配置中,可以使用destroy-method属性。在Java配置中,可以设置@BeandestroyMethod属性。

如下所示:

  • XML
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
  • Java
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

上面例子和下面作用一致:

  • XML
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
  • Java
public class AnotherExampleBean implements DisposableBean {

    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

当然,最上面的例子和Spring解耦。

可以为元素的destroy-method属性指定一个特殊(推断)值,该值指示spring自动检测特定bean类上的公共closeshutdown。(可以实现java.lang.AutoCloseablejava.io.Closeable

你还可以在元素的默认default-destroy-method属性上设置此特殊(推断)值,以将此行为应用于整个bean集(请参见默认初始化和销毁方法)。注意,这是Java配置的默认行为。

默认的初始化和销毁方法

在编写不使用Spring特定的InitializingBeanDisposableBean回调接口的初始化和销毁方法回调时,通常会命名为init()initialize()dispose()

理想情况下,这样的生命周期回调方法的名称在项目中是标准化的,这样所有开发人员都使用相同的方法名称并确保一致性。

你可以配置Spring容器去“查找”特定名字的初始化和销毁方法。这意味着,作为应用程序开发人员,你可以编写应用程序类并使用名为init()的初始化回调,而无需为每个bean定义配置init-method=“init”属性。Spring IOC容器在创建bean时将会调用该方法。此功能强调命名的一致性。

假设初始化回调方法名为init(),销毁回调方法名为destroy()

如下所示:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

接下来,你可以在xml中定义它:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

中的default-init-method表明所有的bean在Spring IOC容器进行初始化的时候,都会在合适的时间调用 init方法。

同样的,你可以在中配置default-destroy-method

如果想覆盖默认的回调函数,可以在中使用init-method或者destroy-method

Spring容器保证在向bean提供所有依赖项之后立即调用配置的初始化回调。因此,在对原始bean引用调用初始化回调时,AOP拦截器等尚未作用于bean。

首先创建一个目标bean,然后才能应用一个带有拦截器链的AOP代理。如果目标bean和代理是单独定义的,那么你的代码甚至可以影响原始目标bean。因此,将拦截器应用于init方法会导致不一致的结果,因为这样做会将目标bean的生命周期耦合到其代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。

总结生命周期机制

在Spring2.5中,你有3种方式来控制bean的生命周期:

  • InitializingBeanDisposableBean回调接口。
  • 自定义init()destroy() 方法。
  • @PostConstruct@PreDestroy注解。

你可以将3种方式结合使用。

如果多种方式一起使用,每种方式都配置了一个不同的方法名,那么他们的执行顺序将会如下面的顺序所示。 但是如果他们都配置了同一个方法名,那么该方法只会执行一次。

如果为initialization配置了多种生命周期的多个名字,那么执行顺序如下:

  • @PostConstruct 的方法注解
  • InitializingBean接口里面的afterPropertiesSet() 方法
  • 自定义的init() 方法

Destroy也是一样的顺序:

  • @PreDestroy 的方法注解
  • DisposableBean接口里面的destroy() 方法
  • 自定义的destroy() 方法

启动和关闭回调

Lifecycle接口为需要管理自己生命周期的对象(例如需要启动和关闭一些后台进程)提供了最基本的方法。

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何Spring管理的对象都可以实现Lifecycle接口。当ApplicationContext收到一个start或者stop信号时,他会将该信号传递给所有的Lifecycle接口的实现。

下面是通过LifecycleProcessor委托来实现的:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

LifecycleProcessorLifecycle的拓展。它还添加了另外两种方法来响应正在刷新和关闭的上下文。

请注意,org.springframework.context.Lifecycle接口只是提供了一个简单的startstop通知协议,但是并没有实现上下文刷新时自动启动的功能。如果想更好的控制自动启动的功能,可以实现org.springframework.context.SmartLifecycle接口。

另外,stop通知不能保证一定在销毁回调之前执行。通常情况下所有的Lifecycle都会收到一个stop通知在总的销毁回调执行之前。但是在热刷新或者终止刷新尝试时,只会执行destroy方法。

启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖”关系,则依赖方在依赖之后开始,在依赖之前停止。然而,有时,直接依赖性是未知的。你可能只知道某个类型的对象应该先于另一个类型的对象开始。在这些情况下,SmartLifecycle接口定义了另一个选项,即在其父接口上定义的getPhase()方法。

下面是Phased接口的定义:

public interface Phased {

    int getPhase();
}

下面是SmartLifecycle的定义:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,具有最小phase的对象首先启动。停止时,按相反顺序执行。

因此,实现SmartLifecycle且其getPhase()方法返回Integer.MIN_VALUE值的对象将是第一个开始和最后一个停止的对象。

同样的,Integer.MAX_VALUE值的phase值将表面对象应在最后启动并首先停止(可能是因为它取决于要运行的其他进程)。在考虑phase值时,还必须知道不实现SmartLifecycle的任何“正常”生命周期对象的默认阶段是0。因此,任何负phase值都表示一个对象应该在这些标准组件之前开始(并在它们之后停止)。对于任何正phase值,顺序相反。

SmartLifecycle定义的Stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。在需要的时候,可以实现异步的关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor在每个phase中的对象中调用该回调时,会等待一个超时值。每个phase默认超时为30秒。你可以通过在上下文中定义一个名为lifecycleProcessor的bean来覆盖默认的生命周期处理器实例。

如果只想修改超时,那么定义以下内容就足够了:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。当stop()被显示调用的时候会启动shutdown的进程,但是这个方法只会在上下文关闭时被调用。另一方面,“refresh(刷新)”回调启用了SmartLifecycle bean的另一个功能。刷新上下文时(在所有对象都已实例化和初始化之后),将调用该回调。此时,默认的生命周期处理器检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。

如果为true,则该对象将在该点启动,而不是等待显式调用上下文或其自身的start()方法(与上下文refresh不同,对于标准上下文实现,不会自动触发)。如前所述,phase值和任何依赖关系决定启动顺序。

在非Web应用程序中优雅的关闭Spring IoC容器

本小节只适用于非web的应用程序。当web应用程序关闭时,Spring的web基础的ApplicationContext实现,已经有代码优雅的关闭 Spring IoC 容器。

如果你使用Spring IOC容器在非web应用程序环境中(富文本桌面程序),需要注册一个shutdown hook到JVM中。这样将保证优雅的关闭,并且在单例bean中调用相关的销毁方法,让所有的资源得到释放。当然,你必须正确的配置和实现这些销毁方法。

调用ConfigurableApplicationContext接口中的registerShutdownHook()来注册一个shutdown hook, 如下所示:

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}

1.6.2 ApplicationContextAware 和 BeanNameAware

ApplicationContext创建了org.springframework.context.ApplicationContextAware接口的一个实现对象时。实例提供了一个对ApplicationContext的引用。下面是ApplicationContextAware的定义:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean可以手动控制创建他的ApplicationContext,可以通过ApplicationContext接口,也可以将这个引用转换成ApplicationContext的子类(比如提供了额外功能的ConfigurableApplicationContext)。

他的一个应用就是在程序中获取其他的beans。这种使用方式有时候会很有用,但是因为其和Spring耦合在一起,违背了IOC原则,所以应该避免这样使用。应该将协作者设置成bean的属性值。

ApplicationContext的其他方法提供了文件资源,发布应用程序事件和访问MessageSource的功能。

在Spring2.5中,自动装载是另外一个获取到ApplicationContext引用的方法。传统的constructorbyType自动装载模式,能将ApplicationContext当做constructor的参数,或者setter方法的参数。为了更灵活的使用,比如自动装载字段或者方法的多个参数,可以使用@Autowired注解在字段或者方法上。这样ApplicationContext将会自动装配到需要的地方。

ApplicationContext创建了一个org.springframework.beans.factory.BeanNameAware的实现类时,这个类提供了对其在关联对象中定义的名字的引用。

如下所示:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

在填充普通bean属性之后,在初始化回调(如InitializingBeanafterPropertiesSet或自定义init方法)之前该回调会被调用。

1.6.3 其他的Aware接口

除了ApplicationContextAwareBeanNameAware,Spring提供了很多Aware回调接口,让bean向容器表明它们需要依赖某种基础结构。

通常来说,名字表明了依赖的类型,下表是非常重要的Aware接口列表:

名字注入依赖
ApplicationContextAware声明ApplicationContext
ApplicationEventPublisherAware封闭ApplicationContext的事件发布者。
BeanClassLoaderAware装载bean classes的类加载器
BeanFactoryAware声明BeanFactory
BeanNameAware声明bean的名字
BootstrapContextAware容器运行的BootstrapContext资源适配器,通常只用在 JCA-aware 的ApplicationContext实例中
LoadTimeWeaverAware在加载时为处理类定义而定义的weaver。
MessageSourceAware处理消息所配置的策略(支持参数化和国际化)
NotificationPublisherAwareSpring JMX 通知publisher
ResourceLoaderAware为资源的low-level访问配置加载器
ServletConfigAware当前容器运行的ServletConfig,只在web-aware 的Spring ApplicationContext有效
ServletContextAware当前容器运行的ServletContext,只在web-aware 的Spring ApplicationContext有效

再次强调,使用这些接口将会把你的代码和Spring API相耦合,违背了控制反转原则。

因此我们只推荐需要访问容器的基础架构bean中使用它们。