本文章主要围绕bean的生命周期来进行讲解,比如bean的实例化之后可以做什么,还有bean销毁之前该做什么
1. 生命周期回调
开发者通过实现Spring的InitializeingBean
和DisposableBean
接口,就可以让容器来管理Bean的生命周期。容器会调用afterPropertiesSet
()前和destroy()
后才会允许Bean在初始化和销毁Bean的时候执行一些操作。
JSR-250的
@PostConstruc
t和@PreDestroy注解
就是现代Spring应用生命周期回调的最佳实践。使用这些注解意味着Bean不在耦合在Spring特定的接口上。详细内容,后续将会介绍。
如果开发者不想使用JSR-250的注解,仍然可以考虑使用init-method
和destroy-method
定义来解耦。
内部来说
,Spring框架使用BeanPostProcessor
的实现来处理任何接口的回调,BeanPostProcessor能够找到并调用合适的方法。如果开发者需要一些Spring并不直接提供的生命周期行为,开发者可以自行实现一个BeanPostProcessor。更多的信息可以参考后面的容器扩展点。
除了初始化和销毁回调,Spring管理的对象也实现了Lifecycle
接口来让管理的对象在容器的生命周期内启动和关闭
接下来会按照下面的目录来讲
初始化回调(有三种方式)
- 实现
InitializingBean
接口,然后重写afterPropertiesSet()
方法- 使用
@PostConstruct
- 指定bean使用
init-method
属性,也可以用基于javabean的配置形式在@Bean之中指定initMethod属性
销毁回调(也有三种方式)
- 实现
DisposableBean
接口,然后重写destroy()
方法- 使用
@PreDestroy
- 指定bean使用
destroy-method
属性,也可以用基于javabean的配置形式在@Bean之中指定destroyMethod属性
联合生命周期机制
启动和关闭回调
在非Web应用关闭Spring IoC容器
-
初始化回调
具体的演示的bean如下:
/**
* @Project: spring
* @description: 初始化回调的方法:主要有三种方式实现
* 调用过程如下被执行:
* 1.有@PostConstruct注解的方法
* 2.实现了InitializingBean接口重写了afterPropertiesSet的方法被执行
* 3.init-method指定的方法被执行
*
* @author: sunkang
* @create: 2018-09-15 18:10
* @ModificationHistory who when What
**/
public class InitializingBeanDemo implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("调用afterPropertiesSet初始化方法");
}
public void init(){
System.out.println("调用init初始化方法");
}
@PostConstruct
public void postConstrct(){
System.out.println("调用postConstrct初始化方法");
}
public InitializingBeanDemo() {
System.out.println("初始化InitializingBeanDemo对象");
}
}
在spring-lifecycle.xml的具体的配置,需要开启注解配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解-->
<context:annotation-config/>
<bean id="initializing" class="com.spring.lifecycle.InitializingBeanDemo" init-method="init"/>
</beans>
具体的测试案例
/**
* @Project: spring
* @description: 测试
* @author: sunkang
* @create: 2018-09-15 18:14
* @ModificationHistory who when What
**/
public class LifeCycleTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("lifecycle/spring-lifecycle.xml");
}
具体的测试结果信息如下: 可以发现现实初始化之后,才进行初始化方法的回调,回调的顺序是postConstrct--》afterPropertiesSet---》init-method
初始化InitializingBeanDemo对象
调用postConstrct初始化方法
调用afterPropertiesSet初始化方法
调用init初始化方法
总结:Spring团队建议开发者不要使用
InitializingBean
接口,因为这样会将不必要的代码耦合到Spring之上。而通过使用@PostConstruct注解或者指定一个POJO的实现方法,比实现接口要更好。在基于XML的配置元数据上,开发者可以使用init-method属性来指定一个没有参数的方法
-
销毁回调
具体的演示如下:
/**
* @Project: spring
* @description: 销毁回调 主要也有三种方式
* 执行的调用顺序如下:
* 1. @PreDestroy注解所指明的preDestroy方法
* 2. DisposableBean的destroy的方法
* 3. destroy-method指定的destroyMethod方法
* @author: sunkang
* @create: 2018-09-15 18:19
* @ModificationHistory who when What
**/
public class DisposableBeanDemo implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("调用DisposableBean的destroy的销毁方法");
}
public void destroyMehtod(){
System.out.println("调用destroyMehtod的销毁方法");
}
@PreDestroy
public void preDestroy(){
System.out.println("调用preDestroy的销毁方法");
}
public DisposableBeanDemo() {
System.out.println("初始化DisposableBeanDemo");
}
}
在spring-lifecycle.xml的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解-->
<context:annotation-config/>
<bean id = "destroy" class="com.spring.lifecycle.DisposableBeanDemo" destroy-method="destroyMehtod"/>
测试方法,如何模拟容器的关闭呢,强迫容器在关闭之前执行销毁的回调方法,可以用容器的close方法来模拟容器的关闭
/**
* @Project: spring
* @description: 测试
* @author: sunkang
* @create: 2018-09-15 18:14
* @ModificationHistory who when What
**/
public class LifeCycleTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("lifecycle/spring-lifecycle.xml");
context.stop();
context.close();
}
}
测试结果:
初始化DisposableBeanDemo
调用preDestroy的销毁方法
调用DisposableBean的destroy的销毁方法
调用destroyMehtod的销毁方法
总结:
同InitializingBean同样,Spring团队仍然不建议开发者来使用DisposableBean回调接口,因为这样会将开发者的代码耦合到Spring代码上。换种方式,比如使用@PreDestroy注解或者指定一个Bean支持的配置方法,比如在基于XML的配置元数据中,开发者可以在Bean标签上指定destroy-method属性。而在Java配置中,开发者可以配置@Bean的destroyMethod。
联合生命周期机制
通过初始化回调和销毁回调的演示可以看出:
如果一个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增加了另外的两个方法来针对上下文的刷新
和关闭做出反应
。
启动和关闭调用是很重要的。如果不同的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的启动的过程
package com.spring.lifecycle;
import org.springframework.context.Lifecycle;
import org.springframework.context.LifecycleProcessor;
import org.springframework.context.SmartLifecycle;
/**
* @Project: spring
* @description: SmartLifecycleDemo
* @author: sunkang
* @create: 2018-09-15 19:05
* @ModificationHistory who when What
**/
public class SmartLifecycleDemo implements SmartLifecycle {
private boolean isRunning = false;
/**
* 1. 我们主要在该方法中启动任务或者其他异步服务,比如开启MQ接收消息<br/>
* 2. 当上下文被刷新(所有对象已被实例化和初始化之后)时,将调用该方法,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。
* 如果为“true”,则该方法会被调用,而不是等待显式调用自己的start()方法。
*/
@Override
public void start() {
System.out.println("start");
// 执行完其他业务后,可以修改 isRunning = true
isRunning = true;
}
/**
* 如果工程中有多个实现接口SmartLifecycle的类,则这些类的start的执行顺序按getPhase方法返回值从小到大执行。<br/>
* 例如:1比2先执行,-1比0先执行。 stop方法的执行顺序则相反,getPhase返回值较大类的stop方法先被调用,小的后被调用。
*/
@Override
public int getPhase() {
System.out.println("getPhase");
// 默认为0
return 0;
}
/**
* 根据该方法的返回值决定是否执行start方法。<br/>
* 返回true时start方法会被自动执行,返回false则不会。
*/
@Override
public boolean isAutoStartup() {
System.out.println("isAutoStartup");
// 默认为false
return true;
}
/**
* 1. 只有该方法返回false时,start方法才会被执行。<br/>
* 2. 只有该方法返回true时,stop(Runnable callback)或stop()方法才会被执行。
*/
@Override
public boolean isRunning() {
System.out.println("isRunning");
// 默认返回false
return isRunning;
}
/**
* SmartLifecycle子类的才有的方法,当isRunning方法返回true时,该方法才会被调用。
*/
@Override
public void stop(Runnable callback) {
System.out.println("stop(Runnable)");
// 如果你让isRunning返回true,需要执行stop这个方法,那么就不要忘记调用callback.run()。
// 否则在你程序退出时,Spring的DefaultLifecycleProcessor会认为你这个TestSmartLifecycle没有stop完成,程序会一直卡着结束不了,等待一定时间(默认超时时间30秒)后才会自动结束。
// PS:如果你想修改这个默认超时时间,可以按下面思路做,当然下面代码是springmvc配置文件形式的参考,在SpringBoot中自然不是配置xml来完成,这里只是提供一种思路。
// <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
// <!-- timeout value in milliseconds -->
// <property name="timeoutPerShutdownPhase" value="10000"/>
// </bean>
callback.run();
isRunning = false;
}
/**
* 接口Lifecycle的子类的方法,只有非SmartLifecycle的子类才会执行该方法。<br/>
* 1. 该方法只对直接实现接口Lifecycle的类才起作用,对实现SmartLifecycle接口的类无效。<br/>
* 2. 方法stop()和方法stop(Runnable callback)的区别只在于,后者是SmartLifecycle子类的专属。
*/
@Override
public void stop() {
System.out.println("stop");
isRunning = false;
}
}
在spring-smartlifecycle.xml的配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="lifeStyleDemo" class="com.spring.lifecycle.SmartLifecycleDemo" />
</beans>
测试类:来模拟容器的启动,停止和关闭
/**
* @Project: spring
* @description:SmartLifeCycle 的测试
* @author: sunkang
* @create: 2018-09-17 14:33
* @ModificationHistory who when What
**/
public class SmartLifeCycleTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
context.setConfigLocation("lifecycle/spring-smartlifecycle.xml");
context.refresh();
context.stop();
context.close();
}
}
看下测试结果:
可以发现在容器加载xml的配置和容器调用stop以及close的时候
,getPhase方法被调用了
在加载xml的时候
,会先去判断是否是isRunning
,如果是,则调用isAutoStartup
判断是否是自动启动的,如果是则调用start方法
,
调用stop方法之后
也是需要判断isRunning的状态
然后调用stop(Runnable)
方法,最后是关闭
。
九月 17, 2018 2:41:11 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@23ab930d: startup date [Mon Sep 17 14:41:11 CST 2018]; root of context hierarchy
九月 17, 2018 2:41:11 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [lifecycle/spring-smartlifecycle.xml]
isAutoStartup
getPhase
isRunning
isAutoStartup
start
九月 17, 2018 2:41:11 下午 org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup start
信息: Starting beans in phase 0
九月 17, 2018 2:41:11 下午 org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup stop
信息: Stopping beans in phase 0
getPhase
isRunning
stop(Runnable)
九月 17, 2018 2:41:11 下午 org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@23ab930d: startup date [Mon Sep 17 14:41:11 CST 2018]; root of context hierarchy
getPhase
isRunning
九月 17, 2018 2:41:11 下午 org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup stop
信息: Stopping beans in phase 0
-
在非Web应用关闭Spring IoC容器
如果开发者在非web应用环境使用Spring IoC容器的话, 比如,在桌面客户端的环境下,开发者需要在JVM上注册一个关闭的钩子。来确保在关闭Spring IoC容器的时候能够调用相关的销毁方法来释放掉对应的资源。当然,开发者也必须要正确的配置和实现那些销毁回调
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...
}
}
我们可以看一下registerShutdownHook,里面的源码:发现这里面其实注册了一个钩子,当jvm离开的时候会调用system.exit
方法,会调用Shutdown.exit(status),然后调用 sequence()方法,然后调用 runHooks(),这里面注册的 钩子将会在jvm关闭的时候执行,也就是说jvm离开的时候会调用 AbstractApplicationContext.this.doClose()方法,也就是销毁容器的方法
。
public void registerShutdownHook() {
if (this.shutdownHook == null) {
this.shutdownHook = new Thread() {
public void run() {
synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
AbstractApplicationContext.this.doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
2.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创建了一个实现了org.springframework.beans.factory.BeanNameAware接口的类,那么这个类就可以针对其名字进行配置。
public interface BeanNameAware {
void setBeanName(string name) throws BeansException;
}
这个回调的调用处于属性配置完以后
,但是初始化回调之前
(比如·InitializingBean的afterPropertiesSet()方法以及自定义的初始化方法·等)
下面进行简单的演示:
/**
* @Project: spring
* @description: ApplicationContextAware 和BeanNameAware的演示
* @author: sunkang
* @create: 2018-09-15 19:21
* @ModificationHistory who when What
**/
public class ApplicationContextAwareDemo implements ApplicationContextAware,BeanNameAware {
public void systemName(){
System.out.println("我是ApplicationContextAwareDemo");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext.getBean("com.spring.lifecycle.ApplicationContextAwareDemo#0",ApplicationContextAwareDemo.class).systemName();
}
@Override
public void setBeanName(String s) {
System.out.println(s);
}
}
在xml的配置如下:
<bean class="com.spring.lifecycle.ApplicationContextAwareDemo"/>
测试结果如下: 可以发现如果在容易中没有配置name或者id属性,那么spring容器会自动生成一个唯一的id,命令规则是类名称的全限定名+“#”+序号,实现了BeanNameAware 可以获取bean的名称,实现了ApplicationContextAware可以获取applicationContext容器对象
com.spring.lifecycle.ApplicationContextAwareDemo#0
我是ApplicationContextAwareDemo
3.其他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原则的,除非必要,最好不要使用。