前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承。
定制Bean
生命周期回调
开发者通过实现Spring的InitializeingBean
和DisposableBean
接口,就可以让容器来管理Bean的生命周期。容器会调用afterPropertiesSet()
前和destroy()
后才会允许Bean在初始化和销毁Bean的时候执行一些操作。
JSR-250的
@PostConstruct
和@PreDestroy
注解就是现代Spring应用生命周期回调的最佳实践。使用这些注解意味着Bean不在耦合在Spring特定的接口上。详细内容,后续将会介绍。
如果开发者不想使用JSR-250的注解,仍然可以考虑使用init-method
和destroy-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
属性,比如如下:
public class ExampleBean {
public void init() {
// do some initialization work
}
}
与如下代码一样效果:
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配置中,开发者可以配置@Bean
的destroyMethod
。
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
上面的代码配置和如下配置是等同的:
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是第一段代码是没有耦合到Spring的。
标签的
destroy-method
可以被配置为特殊指定的值,来方便让Spring能够自动的检查到close
或者shutdown
方法(可以实现java.lang.AutoCloseable
或者java.io.Closeable
都会匹配。)这个特殊指定的值可以配置到的
default-destroy-method
来让所有的Bean实现这个行为。
默认初始化和销毁方法
当开发者不使用Spring特有的InitializingBean
和DisposableBean
回调接口来实现初始化和销毁方法的时候,开发者通常定义的方法名字都是好似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.");
}
}
}
标签上面的default-init-method
属性会让Spring IoC容器识别叫做init
的方法来作为Bean的初始化回调方法。当Bean创建和装载之后,如果Bean有这么一个方法的话,Spring容器就会在合适的时候调用。
类似的,开发者也可以配置默认销毁回调函数,基于XML的配置就在
标签上面使用default-destroy-method
属性。
当存在一些Bean的类有了一些回调函数,而和配置的默认回调函数不同的时候,开发者可以通过特指的方式来覆盖掉默认的回调函数。以XML为例,就是通过使用
标签的init-method
和destroy-method
来覆盖掉
中的配置。
Spring容器会做出如下保证,Bean会在装载了所有的依赖以后,立刻就开始执行初始化回调。这样的话,初始化回调只会在直接的Bean引用装载好后调用,而AOP拦截器还没有应用到Bean上。首先目标Bean会完全初始化好,然后,AOP代理以及其拦截链才能应用。如果目标Bean以及代理是分开定义的,那么开发者的代码甚至可以跳过AOP而直接和引用的Bean交互。因此,在初始化方法中应用拦截器会前后矛盾,因为这样做耦合了目标Bean的生命周期和代理/拦截器,还会因为同Bean直接交互而产生奇怪的现象。
联合生命周期机制
在Spring 2.5之后,开发者有三种选择来控制Bean的生命周期行为:
-
InitializingBean
和DisposableBean
回调接口 - 自定义的
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();
}
从上面代码我们可以发现LifecycleProcessor
是Lifecycle
接口的扩展。LifecycleProcessor
增加了另外的两个方法来针对上下文的刷新和关闭做出反应。
常规的
org.springframework.context.Lifecycle
接口只是为明确的开始/停止通知提供一个契约,而并不表示在上下文刷新时自动开始。考虑实现org.springframework.context.SmartLifecycle
接口可以取代在某个Bean的自动启动过程(包括启动阶段)中的细粒度控制。同时,停止通知并不能保证在销毁之前出现:在正常的关闭情况下,所有的Lifecycle
Bean都会在销毁回调准备好之前收到停止停止,然而,在上下文存活期的热刷新或者停止刷新尝试的时候,只会调用销毁方法。
启动和关闭调用是很重要的。如果不同的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
的值得时候,也同时需要了解正常没有实现SmartLifecycle
的Lifecycle
对象的默认值,这个值为0。因此,任何负值将标兵对象会在标准组件启动之前启动,在标准组件销毁以后再进行销毁。
SmartLifecycle
接口也定义了一个stop
的回调函数。任何实现了SmartLifecycle
接口的函数都必须在关闭流程完成之后调用回调中的run()
方法。这样做可以是能异步关闭。而LifecycleProcessor
的默认实现DefaultLifecycleProcessor
会等到配置的超时时间之后再调用回调。默认的每一阶段的超时时间为30秒。开发者可以通过定义一个叫做lifecycleProcessor
的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...
}
}
ApplicationContextAware
和BeanNameAware
当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;
}
这个回调的调用处于属性配置完以后,但是初始化回调之前,比如InitializingBean
的afterPropertiesSet()
方法以及自定义的初始化方法等。
其他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如果没有配置任何内容,是直接使用父Bean的配置信息的,当然了,如果配置了,将会覆盖父Bean的配置。
子Bean会继承父Bean的作用范围,构造参数值,属性值,和覆盖父Bean的方法,可以增加新的值。任何的作用域,初始化方法,销毁方法,或者静态工厂方法配置,开发者都可以覆盖父Bean的配置。
有一些配置都是从子Bean定义中读取的:depends-on,自动装载模式,依赖检查,单例,延迟初始化。
前面的例子通过使用abstract
标签来使父Bean抽象,如果父定义没有指定类,那么久需要使用属性abstract
如下:
父Bean是无法被实例化的,因为它是不完整的,会被标志位abstract
。当使用abstract
的时候,其配置就作为纯模板来使用了。如果尝试在abctract
的父Bean中引用了其他的Bean或者调用getBean()
都会返回错误。容器内部的preInstantiateSingletons()
方法会忽略掉那些定义为抽象的Bean。
ApplicationContext
会默认预初始化所有的单例。因此,如果开发者配置了一个父Bean作为模板,而且其定义了指定的类,那么开发者就必须配置抽象属性为true
,否则,应用上下文会尝试预初始化这个父Bean。