程序员人生 网站导航

Spring核心技术IoC容器(六)

栏目:互联网时间:2016-06-24 13:27:47

前文已描写了Bean的作用域,本文将描写Bean的1些生命周期作用,配置还有Bean的继承。

定制Bean

生命周期回调

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

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

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

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

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

初始化回调

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

void afterPropertiesSet() throws Exception;

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

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean { public void init() { // do some initialization work } }

与以下代码1样效果:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } }

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

烧毁回调

实现了org.springframework.beans.factory.DisposableBean接口的Bean就可以通让容器通过回调来烧毁Bean所用的资源。DisposableBean接口包括了1个方法:

void destroy() throws Exception;

同InitializingBean一样,Spring团队依然不建议开发者来使用DisposableBean回调接口,由于这样会将开发者的代码耦合到Spring代码上。换种方式,比如使用@PreDestroy注解或指定1个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) } }

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

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

默许初始化和烧毁方法

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

开发者可以配置Spring容器来针对每个Bean都查找这类名字的初始化和烧毁回调函数。也就是说,任何的1个利用开发者,都会在利用的类中使用1个叫init()的初始化回调,而不需要在每一个Bean中定义init-method="init"这中属性。Spring IoC容器会在Bean创建的时候调用那个方法(就如前面描写的标准生命周期1样。)这个特性也强迫开发者为其他的初始化和烧毁回调函数使用一样的名字。

假定开发者的初始化回调方法名字为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有这么1个方法的话,Spring容器就会在适合的时候调用。

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

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

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

联合生命周期机制

在Spring 2.5以后,开发者有3种选择来控制Bean的生命周期行动:

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

开发者也能够在Bean上联合这些机制1起使用

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

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

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

烧毁方法的履行顺序和初始化的履行顺序相同:

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

启动和关闭回调

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

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

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

public interface Phased { int getPhase(); }
public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); }

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

SmartLifecycle接口也定义了1个stop的回调函数。任何实现了SmartLifecycle接口的函数都必须在关闭流程完成以后调用回调中的run()方法。这样做可以是能异步关闭。而LifecycleProcessor的默许实现DefaultLifecycleProcessor会等到配置的超时时间以后再调用回调。默许的每阶段的超时时间为30秒。开发者可以通过定义1个叫做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容器

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

如果开发者在非web利用环境使用Spring IoC容器的话, 比如,在桌面客户真个环境下,开发者需要在JVM上注册1个关闭的钩子。来确保在关闭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接口的对象时,该对象的实例会包括1个到ApplicationContext的援用。

public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }

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

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

ApplicationContext创建了1个实现了org.springframework.beans.factory.BeanNameAware接口的类,那末这个类就能够针对其名字进行配置。

public interface BeanNameAware { void setBeanName(string name) throws BeansException; }

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

其他Aware接口

除上面描写的两种Aware接口,Spring还提供了1系列的Aware接口来让Bean告知容器,这些Bean需要1些具体的基础设施信息。最重要的1些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的定义可以包括很多的配置信息,包括构造参数,属性等等,也能够包括1些容器指定的信息,比如初始化方法,工厂方法等等。子Bean会继承父Bean的配置信息。子Bean也能够覆盖父Bean的1些值,或增加1些值。通过定义父Bean和子Bean可以减少配置内容,是1种高效的模板性能。

如果开发者通过编程的方式跟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的配置。

有1些配置都是从子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会默许预初始化所有的单例。因此,如果开发者配置了1个父Bean作为模板,而且其定义了指定的类,那末开发者就必须配置抽象属性为true,否则,利用上下文会尝试预初始化这个父Bean。

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐