A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 Dream丿拼 于 2018-3-24 20:49 编辑

本文将描述Bean的一些生命周期作用,配置还有Bean的继承。
生命周期回调开发者通过实现Spring的InitializeingBean和DisposableBean接口,就可以让容器来管理Bean的生命周期。容器会调用afterPropertiesSet()前和destroy()后才会允许Bean在初始化和销毁Bean的时候执行一些操作。
内部来说,Spring框架使用BeanPostProcessor的实现来处理任何接口的回调,BeanPostProcessor能够找到并调用合适的方法。如果开发者需要一些Spring并不直接提供的生命周期行为,开发者可以自行实现一个BeanPostProcessor。更多的信息可以参考后面的容器扩展点。

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

初始化回调
org.springframework.beans.factory.InitializingBean接口允许Bean在所有的必要的依赖配置配置完成后来执行初始化Bean的操作。InitializingBean接口中特指了一个方法:void afterPropertiesSet() throws Exception;
[Java] 纯文本查看 复制代码
public class ExampleBean {

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

}

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

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

  • 1  void destroy() throws Exception;

同InitializingBean同样,Spring团队仍然不建议开发者来使用DisposableBean回调接口,因为这样会将开发者的代码耦合到Spring代码上。换种方式,比如使用@PreDestroy注解或者指定一个Bean支持的配置方法,比如在基于XML的配置元数据中,开发者可以在Bean标签上指定destroy-method属性。而在Java配置中,开发者可以配置@Bean的destroyMethod。
[Java] 纯文本查看 复制代码
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>

[Java] 纯文本查看 复制代码
public class ExampleBean {

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

}


默认初始化和销毁方法当开发者不使用Spring特有的InitializingBean和DisposableBean回调接口来实现初始化和销毁方法的时候,开发者通常定义的方法名字都是好似init(),initialize()或者是dispose()等等。那么,想这类的方法就可以标准化,来让所有的开发者都使用一样的名字来确保一致性。
开发者可以配置Spring容器来针对每一个Bean都查找这种名字的初始化和销毁回调函数。也就是说,任何的一个应用开发者,都会在应用的类中使用一个叫init()的初始化回调,而不需要在每个Bean中定义init-method="init"这中属性。Spring IoC容器会在Bean创建的时候调用那个方法(就如前面描述的标准生命周期一样。)这个特性也强制开发者为其他的初始化以及销毁回调函数使用同样的名字。

假设开发者的初始化回调方法名字为init()而销毁的回调方法为destroy()。那么开发者的类就会好似如下的代码:
[Java] 纯文本查看 复制代码
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.");
        }
    }

}

[Java] 纯文本查看 复制代码
<beans default-init-method="init">

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

</beans>

<beans/>标签上面的default-init-method属性会让Spring IoC容器识别叫做init的方法来作为Bean的初始化回调方法。当Bean创建和装载之后,如果Bean有这么一个方法的话,Spring容器就会在合适的时候调用。
类似的,开发者也可以配置默认销毁回调函数,基于XML的配置就在<beans/>标签上面使用default-destroy-method属性。
当存在一些Bean的类有了一些回调函数,而和配置的默认回调函数不同的时候,开发者可以通过特指的方式来覆盖掉默认的回调函数。以XML为例,就是通过使用<bean>标签的init-method和destroy-method来覆盖掉<beans/>中的配置。

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

联合生命周期机制在Spring 2.5之后,开发者有三种选择来控制Bean的生命周期行为:
  • InitializingBean和DisposableBean回调接口
  • 自定义的init()以及destroy方法
  • 使用@PostConstruct以及@PreDestroy注解

开发者也可以在Bean上联合这些机制一起使用
如果一个Bean配置了多个生命周期机制,并且含有不同的方法名,执行的顺序如下:
  • 包含@PostConstruct注解的方法
  • 在InitializingBean接口中的afterPropertiesSet()方法
  • 自定义的init()方法
销毁方法的执行顺序和初始化的执行顺序相同:

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

启动和关闭回调Lifecycle接口中为任何有自己生命周期需求的对象定义了基本的方法(比如启动和停止一些后台进程):
[Java] 纯文本查看 复制代码
public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();

}

任何Spring管理的对象都可实现上面的接口。那么当ApplicationContext本身受到了启动或者停止的信号时,ApplicationContext会通过委托LifecycleProcessor来串联上下文中的Lifecycle的实现。
[Java] 纯文本查看 复制代码
public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

从上面代码我们可以发现LifecycleProcessor是Lifecycle接口的扩展。LifecycleProcessor增加了另外的两个方法来针对上下文的刷新和关闭做出反应。
启动和关闭调用是很重要的。如果不同的Bean之间存在depends-on的关系的话,被依赖的一方需要更早的启动,而且关闭的更早。然而,有的时候直接的依赖是未知的,而开发者仅仅知道哪一种类型需要更早进行初始化。在这种情况下,SmartLifecycle接口定义了另一种选项,就是其父接口Phased中的getPhase()方法。
[Java] 纯文本查看 复制代码
public interface Phased {

    int getPhase();

}

[Java] 纯文本查看 复制代码
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);

}

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

SmartLifecycle接口也定义了一个stop的回调函数。任何实现了SmartLifecycle接口的函数都必须在关闭流程完成之后调用回调中的run()方法。这样做可以是能异步关闭。而LifecycleProcessor的默认实现DefaultLifecycleProcessor会等到配置的超时时间之后再调用回调。默认的每一阶段的超时时间为30秒。开发者可以通过定义一个叫做lifecycleProcessor的Bean来覆盖默认的生命周期处理器。如果开发者需要配置超时时间,可以通过如下代码:
[Java] 纯文本查看 复制代码
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

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

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

如果开发者通过编程的方式跟ApplicationContext交流,就会知道子Bean是通过ChildBeanDefinition类表示的。大多数的开发者不需要再这个级别上来跟子Bean定义交互,只需要在ClassPathXmlApplicationContext中显式的配置Bean就可以了。当使用基于XML的元数据配置的时候,开发者通过使用parent属性来定义子Bean,如下所示:
[Java] 纯文本查看 复制代码
<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>

如上述代码所示,子Bean如果没有配置任何内容,是直接使用父Bean的配置信息的,当然了,如果配置了,将会覆盖父Bean的配置。
子Bean会继承父Bean的作用范围,构造参数值,属性值,和覆盖父Bean的方法,可以增加新的值。任何的作用域,初始化方法,销毁方法,或者静态工厂方法配置,开发者都可以覆盖父Bean的配置。
有一些配置都是从子Bean定义中读取的:depends-on,自动装载模式,依赖检查,单例,延迟初始化。

前面的例子通过使用abstract标签来使父Bean抽象,如果父定义没有指定类,那么久需要使用属性abstract如下:
[Java] 纯文本查看 复制代码
<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>

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





0 个回复

您需要登录后才可以回帖 登录 | 加入黑马