Spring核心技术IoC容器(六)

前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有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;

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

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

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

}

与如下代码一样效果:

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

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

}

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

销毁回调

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

void destroy() throws Exception;

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

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

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

}

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

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

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

}

但是第一段代码是没有耦合到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.");
        }
    }

}
<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-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();

}

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

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

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

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

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

public interface Phased {

    int getPhase();

}
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);

}

当启动时,拥有最低的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>

和前文提到的,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...

    }
}

ApplicationContextAwareBeanNameAware

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

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

这样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;

}

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

其他Aware接口

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

名字 注入的依赖
ApplicationContextAware 声明的ApplicationContext
ApplicationEventPlulisherAware ApplicationContext中的事件发布器
BeanClassLoaderAware 加载Bean使用的类加载器
BeanFactoryAware 声明的BeanFactory
BeanNameAware Bean的名字
BootstrapContextAware 容器运行的资源适配器BootstrapContext,通常仅在JCA环境下有效
LoadTimeWeaverAware 加载期间处理类定义的weaver
MessageSourceAware 解析消息的配置策略
NotificationPublisherAware Spring 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>

如上述代码所示,子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>

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

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

时间: 2024-10-17 23:14:48

Spring核心技术IoC容器(六)的相关文章

Spring核心技术IoC容器(八)

本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } //

Spring核心技术IoC容器(五)

前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了具体如何创建Bean实例的步骤.这些步骤是很重要的,因为只有通过这些配置,开发者才能创建实例对象. 开发者不仅可以控制多种多样的依赖到Bean之中,也可以配置Bean的作用域.这种方法是非常强大而且弹性也非常好,开发者可以通过配置来指定对象的作用域,而不用在Java类层次上来配置.Bean可以配置多种作用域.Spring框架支持5中作用域,有三种

Spring 核心技术 IoC容器(一)

IoC 容器 IoC容器和Bean简介 这章包括了Spring框架对于IoC规则的实现.Ioc也同DI(依赖注入).而对象是通过构造函数,工厂方法,或者一些Set方法来定义对象之间的依赖的.容器在创建这些Bean对象的时候同时就会注入这些依赖.这个过程是根本上的反转了,不再由Bean本身来控制实例化和定位依赖,而是通过服务定位来控制这个过程,也是IoC(控制反转)的由来. org.springframework.beans和org.springframework.context包是Spring框

Spring核心技术IoC容器(四)

前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也就是另一个Bean的属性之一.多数情况下,开发者可以在配置XML元数据的时候使用<ref/>标签.然而,有时Bean之间的依赖关系不是直接关联的.比如:需要调用类的静态实例化器来出发,类似数据库驱动注册.depends-on属性会使明确的强迫依赖的Bean在引用之前就会初始化.下面的例子使用dep

Spring核心技术IoC容器(七)

本文将讨论如何关于在Spring生命周期中扩展Spring中的Bean功能. 容器的扩展 通常来说,开发者不需要通过继承ApplicationContext来实现自己的子类扩展功能.但是Spring IoC容器确实可以通过实现接口来增加一些功能.下面将描述一下这些接口. 通过BeanPostProcessor定义Bean BeanPostProcessor接口定义了一些回调方法,开发者可以通过实现来自己的实例化逻辑,依赖解析逻辑等等.如果开发者只是想在Spring容器完成了实例化,配置以及初始化

Spring 核心技术IoC容器(二)

本文将继续前文,描述Spring IoC中的依赖处理. 依赖 一般的企业应用也不会只有一个对象(或者是Spring Bean).甚至最简单的应用都要有一些对象来协同工作来让终端用户看到一个完整的应用.下一部分将解释开发者从单独的定义Bean,到让这些对象在一个应用中协同工作. 依赖注入 依赖注入是一个让对象只通过构造参数,工厂方法的参数或者配置的属性来定义他们的依赖.这些依赖也是对象所需要协同工作的对象.容器在之后会在创建Bean的时候注入这些依赖.整个过程完全反转了Bean自己控制实例化或者,

Spring 核心技术IoC容器 (三)

本文将继续前文,针对依赖注入的细节进行描述 依赖注入细节 如前文所述,开发者可以通过定义Bean的依赖的来引用其他的Bean或者是一些值.Spring基于XML的配置元数据支持一些子元素<property/>以及<constructor-arg/>来达到这一目的. 内在值类型(Java Primitives类型,字符串等) 元素<property/>有value属性来以易读的形式配置一个属性或者构造参数.Spring的遍历就是用来讲这些字符串的值转换成指定的类型. &l

Spring之IOC容器加载初始化的方式

引言 我们知道IOC容器时Spring的核心,可是如果我们要依赖IOC容器对我们的Bean进行管理,那么我们就需要告诉IOC容易他需要管理哪些Bean而且这些Bean有什么要求,这些工作就是通过通过配置文件告诉Spring 的IOC容器.在我们的完成这些配置文件以后,如果IOC容易要实现对这些Bean的管理,除了资源的定位还有一个很重要的一步就是完成IOC加载初始化,也就是配置文件的载入过程.完成IOC容器加载初始化的方式只要有三种,第一种就是通过File文件加载,第二种是通过Classpath

Spring的IOC容器—依赖注入

前面一篇博客大致讲了一下Spring的IOC容器的原理,IOC即控制反转主要是依靠依赖注入的方式来实现的.依赖注入是指所依赖的对象不是由自己new出来的,而是用别的方式像打针似的注入进来. 其实说白了不管是控制反转还是依赖注入都说明了Spring采用动态.灵活的方式来管理各种对象. Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理.有以下几种注入方式: 1. Setter 注入 因为对于javaBean来说,我们可以通过setter和getter方法