Spring核心--bean的生命周期

定制Bean

生命周期回调

开发者通过实现Spring的InitializeingBeanDisposableBean接口,就可以让容器来管理Bean的生命周期。容器会调用afterPropertiesSet()前和destroy()后才会允许Bean在初始化和销毁Bean的时候执行一些操作。

JSR-250的@PostConstruct@PreDestroy注解就是现代Spring应用生命周期回调的最佳实践。使用这些注解意味着Bean不在耦合在Spring特定的接口上。详细内容,后续将会介绍。 
如果开发者不想使用JSR-250的注解,仍然可以考虑使用init-methoddestroy-method定义来解耦。

内部来说,Spring框架使用BeanPostProcessor的实现来处理任何接口的回调,BeanPostProcessor能够找到并调用合适的方法。如果开发者需要一些Spring并不直接提供的生命周期行为,开发者可以自行实现一个BeanPostProcessor。更多的信息可以参考后面的容器扩展点。

除了初始化和销毁回调,Spring管理的对象也实现了Lifecycle接口来让管理的对象在容器的生命周期内启动和关闭。

生命周期回调在本节会进行详细描述。

初始化回调

org.springframework.beans.factory.InitializingBean接口允许Bean在所有的必要的依赖配置配置完成后来执行初始化Bean的操作。InitializingBean接口中特指了一个方法:

void afterPropertiesSet() throws Exception;
  • 1

Spring团队建议开发者不要使用InitializingBean接口,因为这样会不必要的将代码耦合到Spring之上。而通过使用@PostConstruct注解或者指定一个POJO的实现方法,比实现接口要更好。在基于XML的配置元数据上,开发者可以使用init-method属性来指定一个没有参数的方法。使用Java配置的开发者可以使用@Bean之中的initMethod属性,比如如下:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
  • 1
public class ExampleBean {

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

与如下代码一样效果:

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

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

但是前一个版本的代码是没有耦合到Spring的。

销毁回调

实现了org.springframework.beans.factory.DisposableBean接口的Bean就能通让容器通过回调来销毁Bean所用的资源。DisposableBean接口包含了一个方法:

void destroy() throws Exception;
  • 1

同InitializingBean同样,Spring团队仍然不建议开发者来使用DisposableBean回调接口,因为这样会将开发者的代码耦合到Spring代码上。换种方式,比如使用@PreDestroy注解或者指定一个Bean支持的配置方法,比如在基于XML的配置元数据中,开发者可以在Bean标签上指定destroy-method属性。而在Java配置中,开发者可以配置@BeandestroyMethod

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

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面的代码配置和如下配置是等同的:

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

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

但是第一段代码是没有耦合到Spring的。

<bean/>标签的destroy-method可以被配置为特殊指定的值,来方便让Spring能够自动的检查到close或者shutdown方法(可以实现java.lang.AutoCloseable或者java.io.Closeable都会匹配。)这个特殊指定的值可以配置到<beans/>default-destroy-method来让所有的Bean实现这个行为。

默认初始化和销毁方法

当开发者不使用Spring特有的InitializingBeanDisposableBean回调接口来实现初始化和销毁方法的时候,开发者通常定义的方法名字都是好似init()initialize()或者是dispose()等等。那么,想这类的方法就可以标准化,来让所有的开发者都使用一样的名字来确保一致性。

开发者可以配置Spring容器来针对每一个Bean都查找这种名字的初始化和销毁回调函数。也就是说,任何的一个应用开发者,都会在应用的类中使用一个叫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.");
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
<beans default-init-method="init">

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

</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

<beans/>标签上面的default-init-method属性会让Spring IoC容器识别叫做init的方法来作为Bean的初始化回调方法。当Bean创建和装载之后,如果Bean有这么一个方法的话,Spring容器就会在合适的时候调用。

类似的,开发者也可以配置默认销毁回调函数,基于XML的配置就在<beans/>标签上面使用default-destroy-method属性。

当存在一些Bean的类有了一些回调函数,而和配置的默认回调函数不同的时候,开发者可以通过特指的方式来覆盖掉默认的回调函数。以XML为例,就是通过使用<bean>标签的init-methoddestroy-method来覆盖掉<beans/>中的配置。

Spring容器会做出如下保证,Bean会在装载了所有的依赖以后,立刻就开始执行初始化回调。这样的话,初始化回调只会在直接的Bean引用装载好后调用,而AOP拦截器还没有应用到Bean上。首先目标Bean会完全初始化好,然后,AOP代理以及其拦截链才能应用。如果目标Bean以及代理是分开定义的,那么开发者的代码甚至可以跳过AOP而直接和引用的Bean交互。因此,在初始化方法中应用拦截器会前后矛盾,因为这样做耦合了目标Bean的生命周期和代理/拦截器,还会因为同Bean直接交互而产生奇怪的现象。

联合生命周期机制

在Spring 2.5之后,开发者有三种选择来控制Bean的生命周期行为:

  • InitializingBeanDisposableBean回调接口
  • 自定义的init()以及destroy方法
  • 使用@PostConstruct以及@PreDestroy注解

开发者也可以在Bean上联合这些机制一起使用

如果Bean配置了多个生命周期机制,而且每个机制配置了不同的方法名字,那么每个配置的方法会按照后面描述的顺序来执行。然而,如果配置了相同的名字,比如说初始化回调为init(),在不止一个生命周期机制配置为这个方法的情况下,这个方法只会执行一次。

如果一个Bean配置了多个生命周期机制,并且含有不同的方法名,执行的顺序如下:

  • 包含@PostConstruct注解的方法
  • InitializingBean接口中的afterPropertiesSet()方法
  • 自定义的init()方法

销毁方法的执行顺序和初始化的执行顺序相同:

  • 包含@PreDestroy注解的方法
  • DisposableBean接口中的destroy()方法
  • 自定义的destroy()方法

启动和关闭回调

Lifecycle接口中为任何有自己生命周期需求的对象定义了基本的方法(比如启动和停止一些后台进程):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

任何Spring管理的对象都可实现上面的接口。那么当ApplicationContext本身受到了启动或者停止的信号时,ApplicationContext会通过委托LifecycleProcessor来串联上下文中的Lifecycle的实现。

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

从上面代码我们可以发现LifecycleProcessorLifecycle接口的扩展。LifecycleProcessor增加了另外的两个方法来针对上下文的刷新和关闭做出反应。

常规的org.springframework.context.Lifecycle接口只是为明确的开始/停止通知提供一个契约,而并不表示在上下文刷新时自动开始。考虑实现org.springframework.context.SmartLifecycle接口可以取代在某个Bean的自动启动过程(包括启动阶段)中的细粒度控制。同时,停止通知并不能保证在销毁之前出现:在正常的关闭情况下,所有的LifecycleBean都会在销毁回调准备好之前收到停止停止,然而,在上下文存活期的热刷新或者停止刷新尝试的时候,只会调用销毁方法。

启动和关闭调用是很重要的。如果不同的Bean之间存在depends-on的关系的话,被依赖的一方需要更早的启动,而且关闭的更早。然而,有的时候直接的依赖是未知的,而开发者仅仅知道哪一种类型需要更早进行初始化。在这种情况下,SmartLifecycle接口定义了另一种选项,就是其父接口Phased中的getPhase()方法。

public interface Phased {

    int getPhase();

}
  • 1
  • 2
  • 3
  • 4
  • 5
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

当启动时,拥有最低的phased的对象优先启动,而当关闭时,是相反的顺序。因此,如果一个对象实现了SmartLifecycle然后令其getPhase()方法返回了Integer.MIN_VALUE的话,就会让该对象最早启动,而最晚销毁。显然,如果getPhase()方法返回了Integer.MAX_VALUE就说明了该对象会最晚启动,而最早销毁。当考虑到使用phased的值得时候,也同时需要了解正常没有实现SmartLifecycleLifecycle对象的默认值,这个值为0。因此,任何负值将标兵对象会在标准组件启动之前启动,在标准组件销毁以后再进行销毁。

SmartLifecycle接口也定义了一个stop的回调函数。任何实现了SmartLifecycle接口的函数都必须在关闭流程完成之后调用回调中的run()方法。这样做可以是能异步关闭。而LifecycleProcessor的默认实现DefaultLifecycleProcessor会等到配置的超时时间之后再调用回调。默认的每一阶段的超时时间为30秒。开发者可以通过定义一个叫做lifecycleProcessor的Bean来覆盖默认的生命周期处理器。如果开发者需要配置超时时间,可以通过如下代码:

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

和前文提到的,LifecycleProcessor接口定义了回调方法来刷新和关闭山下文。关闭的话,如果stop()方法已经明确调用了,那么就会驱动关闭的流程,但是如果是上下文关闭就不会发生这种情况。而刷新的回调会使能SmartLifecycle的另一个特性。当上下文刷新完毕(所有的对象已经实例化并初始化),那么就会调用回调,默认的生命周期处理器会检查每一个SmartLifecycle对象的isAutoStartup()返回的Bool值。如果为真,对象将会自动启动而不是等待明确的上下文调用,或者调用自己的start()方法(不同于上下文刷新,标准的上下文实现是不会自动启动的)。phased的值以及depends-on关系会决定对象启动和销毁的顺序。

在非Web应用关闭Spring IoC容器

这一部分只是针对于非Web的应用。Spring的基于web的ApplicationContext实现已经有代码在web应用关闭的时候能够优雅的关闭Spring IoC容器。

如果开发者在非web应用环境使用Spring IoC容器的话, 比如,在桌面客户端的环境下,开发者需要在JVM上注册一个关闭的钩子。来确保在关闭Spring IoC容器的时候能够调用相关的销毁方法来释放掉对应的资源。当然,开发者也必须要正确的配置和实现那些销毁回调。

开发者可以在ConfigurableApplicationContext接口调用registerShutdownHook()来注册销毁的钩子:

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(
                new String []{"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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

ApplicationContextAwareBeanNameAware

ApplicationContext在创建实现了org.springframework.context.ApplicationContextAware接口的对象时,该对象的实例会包含一个到ApplicationContext的引用。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}
  • 1
  • 2
  • 3
  • 4
  • 5

这样Bean就能够通过编程的方式操作和创建ApplicationContext了。通过ApplicationContext接口,或者通过将引用转换成已知的接口的子类,比如ConfigurableApplicationContext就能够显式一些额外的功能。其中的一个用法就是可以通过编程的方式来获取其他的Bean。有的时候这个能力很有用,然而,Spring团队推荐最好避免这样做,因为这样会耦合代码到Spring上,同时也没有遵循IoC的风格。其他的ApplicationContext的方法可以提供一些到资源的访问,发布应用事件,或者进入MessageSource。这些信息在后续的针对ApplicationContext的描述中会讲到。

在Spring 2.5版本,自动装载也是获得ApplicationContext的一种方式。传统的构造函数和通过类型的装载方式(前文Spring核心技术IoC容器(四)有相关描述)可以通过构造函数或者是setter方法的方式注入。开发者也可以通过注解注入的方式使用更多的特性。

ApplicationContext创建了一个实现了org.springframework.beans.factory.BeanNameAware接口的类,那么这个类就可以针对其名字进行配置。

public interface BeanNameAware {

    void setBeanName(string name) throws BeansException;

}
  • 1
  • 2
  • 3
  • 4
  • 5

这个回调的调用处于属性配置完以后,但是初始化回调之前,比如InitializingBeanafterPropertiesSet()方法以及自定义的初始化方法等。

其他Aware接口

除了上面描述的两种Aware接口,Spring还提供了一系列的Aware接口来让Bean告诉容器,这些Bean需要一些具体的基础设施信息。最重要的一些Aware接口都在下面表中进行了描述:

名字 注入的依赖
ApplicationContextAware声明的ApplicationContext
ApplicationEventPlulisherAwareApplicationContext中的事件发布器
BeanClassLoaderAware加载Bean使用的类加载器
BeanFactoryAware声明的BeanFactory
BeanNameAwareBean的名字
BootstrapContextAware容器运行的资源适配器BootstrapContext,通常仅在JCA环境下有效
LoadTimeWeaverAware加载期间处理类定义的weaver
MessageSourceAware解析消息的配置策略
NotificationPublisherAwareSpring JMX通知发布器
PortletConfigAware容器当前运行的PortletConfig,仅在web下的Spring ApplicationContext中可见
PortletContextAware容器当前运行的PortletContext,仅在web下的Spring ApplicationContext中可见
ResourceLoaderAware配置的资源加载器
ServletConfigAware容器当前运行的ServletConfig,仅在web下的Spring ApplicationContext中可见
ServletContextAware容器当前运行的ServletContext,仅在web下的Spring ApplicationContext中可见

再次的声明,上面这些接口的使用时违反IoC原则的,除非必要,最好不要使用。

Bean继承

Bean的定义可以包括很多的配置信息,包括构造参数,属性等等,也可以包括一些容器指定的信息,比如初始化方法,工厂方法等等。子Bean会继承父Bean的配置信息。子Bean也可以覆盖父Bean的一些值,或者增加一些值。通过定义父Bean和子Bean可以减少配置内容,是一种高效的模板性能。

如果开发者通过编程的方式跟ApplicationContext交流,就会知道子Bean是通过ChildBeanDefinition类表示的。大多数的开发者不需要再这个级别上来跟子Bean定义交互,只需要在ClassPathXmlApplicationContext中显式的配置Bean就可以了。当使用基于XML的元数据配置的时候,开发者通过使用parent属性来定义子Bean,如下所示:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如上述代码所示,子Bean如果没有配置任何内容,是直接使用父Bean的配置信息的,当然了,如果配置了,将会覆盖父Bean的配置。

子Bean会继承父Bean的作用范围,构造参数值,属性值,和覆盖父Bean的方法,可以增加新的值。任何的作用域,初始化方法,销毁方法,或者静态工厂方法配置,开发者都可以覆盖父Bean的配置。

有一些配置都是从子Bean定义中读取的:depends-on,自动装载模式,依赖检查,单例,延迟初始化。

前面的例子通过使用abstract标签来使父Bean抽象,如果父定义没有指定类,那么久需要使用属性abstract如下:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

父Bean是无法被实例化的,因为它是不完整的,会被标志位abstract。当使用abstract的时候,其配置就作为纯模板来使用了。如果尝试在abctract的父Bean中引用了其他的Bean或者调用getBean()都会返回错误。容器内部的preInstantiateSingletons()方法会忽略掉那些定义为抽象的Bean。

ApplicationContext会默认预初始化所有的单例。因此,如果开发者配置了一个父Bean作为模板,而且其定义了指定的类,那么开发者就必须配置抽象属性为true,否则,应用上下文会尝试预初始化这个父Bean。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值