spring生命周期及衍生出的优雅关机实现原理
总结:
spring的生命周期过程:
形成bean,set属性、注入,init,destory
原理:用处
@PostConstruct spring初始化完成之后调用每个实例中的这个注解的方法 对应配置中的init方法 或者接口中BeanFactoryAware的init
@PreDestroy spring容器关闭之后掉哟给每个实例中的这个注解方法 对应配置中的destroy方法 或者接口中BeanFactoryAware的destory(即可实现hook优雅关机)
每个bean实例化过程都会把有@postConstruct,@PreDestory注解的方法打包成method对象存在内存list中,在上述时机遍历执行,用反射的机制调用执行
hook的主要原理就是用了join---主线程等在加入的子线程执行完才继续
加入hook的方法线程依次进入join
在springboot启动的时候就注册了hook ---AbstractApplicationContext
Terminator 中Signal.handle() 处理中断信号的方法--会通过sh回调Shutdown.exit(),从而出发sequence(),sequence()中调用runhooks方法,
就会调用ApplicationShutdownHooks 中的runHooks方法(遍历所有有@predestory的线程方法)----加入hook的方法线程依次进入join,执行每个有@destory的方法之后执行主线程
详解:
一、引言
在开发中我们如果要在关闭spring容器后释放一些资源,通常的做法有如下几种:
1.在方法上加上@PreDestroy注解
2.实现DisposableBean接口,实现其destroy方法
比较常用的是第一种实现,因为其足够简便。下面就来分析一下它的实现原理,看它是在哪一个环节被触发的。
二、开始分析
我们先移步到CommonAnnotationBeanPostProcessor
这个类中,看如下一段代码:
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);
ignoreResourceType("javax.xml.ws.WebServiceContext");
}
可见在CommonAnnotationBeanPostProcessor的无参构造函数中设置了一个默认的DestroyAnnotationType,即PreDestroy
.在它的上方我们也看到了经常使用的PostConstruct
,其实原理是一致的,只是spring帮我们控制了调用的顺序而已。
而CommonAnnotationBeanPostProcessor是实现了BeanFactoryAware
和BeanFactoryAware
的,也就是spring在启动的时候会找到这些扩展接口的子类型进行实例化。从而实现一些个性化的功能,例如:注解、配置注入、初始化、关闭操作等等。由于本文的重点在PreDestroy
,所以不会过多的讲spring的加载过程。
接下来深入看看在哪里使用到了PreDestroy:
{
if (destroyAnnotationType != null) {
if (method.getAnnotation(destroyAnnotationType) != null) {
currDestroyMethods.add(new LifecycleElement(method));
if (debug) {
logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
}
}
});
initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return new LifecycleMetadata(clazz, initMethods, destroyMethods);
}
看这一段代码,首先判断destroyAnnotationType它是否为空,显然这里不为空,这里指定了PreDestroy
,然后再判断这个注解上是否有@PreDestroy注解,如果有,就将该Method
包装成一个LifecycleElement
添加到一个List中,然后将其添加到了另外一个集合destroyMethods中。接下来看一下哪里在使用这个destroyMethods集合。
移步到InitDestroyAnnotationBeanPostProcessor
中,看到如下代码:
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeDestroyMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
String msg = "Invocation of destroy method failed on bean with name '" + beanName + "'";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex.getTargetException());
}
else {
logger.warn(msg + ": " + ex.getTargetException());
}
}
catch (Throwable ex) {
logger.error("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
}
}
这个findLifecycleMetadata
方法通过调用buildLifecycleMetadata方法最终调用到了最上面的那段代码,该metadata也就持有了所有加了PreDestroy
注解的方法列表。接下来就是利用反射invoke
目标类即可实现。
public void invokeDestroyMethods(Object target, String beanName) throws Throwable {
Collection<LifecycleElement> destroyMethodsToUse =
(this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods);
if (!destroyMethodsToUse.isEmpty()) {
boolean debug = logger.isDebugEnabled();
for (LifecycleElement element : destroyMethodsToUse) {
if (debug) {
logger.debug("Invoking destroy method on bean '" + beanName + "': " + element.getMethod());
}
element.invoke(target);
}
}
}
三、再深入一点
如果想要优雅的退出,@PreDestroy能否满足要求呢?因为我们常用的做法就是注册一个钩子程序,当我们kill进程时(非 kill -9).jvm会收到操作系统一个终端,来做一些资源收尾的操作。
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
logger.info("shutdown hook run.");
try {
} catch (Exception e) {
}
}
}));
如果spring想要优雅退出,必要要借助于hook,不然是没法影响中断的。接下来看一眼spring是怎么实现的。其实在AbstractApplicationContext
中有这样一个方法:
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);
}
}
springboot会通过启动main函数时调用refreshContext
来注册钩子程序:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
所以要想实现优雅关闭资源,使用@PreDestroy注解即可。
四、钩子程序的实现原理
class ApplicationShutdownHooks {
/* The set of registered hooks */
private static IdentityHashMap<Thread, Thread> hooks;
static {
try {
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
//被sequence方法调用
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
// application shutdown hooks cannot be added if
// shutdown is in progress.
hooks = null;
}
}
}
看一下runHooks()方法:
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
}
其实就是把hooks里的线程全部拿到然后启动,并且等待执行结束。添加hook即把线程存放在IdentityHashMap中。当调用Shutdown.add()的时候其实是将该线程存放在Shutdown的成员变量数组中。
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null)
throw new InternalError("Shutdown hook at slot " + slot + " already registered");
if (!registerShutdownInProgress) {
if (state > RUNNING)
throw new IllegalStateException("Shutdown in progress");
} else {
if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
throw new IllegalStateException("Shutdown in progress");
}
hooks[slot] = hook;
}
}
接下来看一下Shutdown的sequence
方法:
private static void sequence() {
synchronized (lock) {
/* Guard against the possibility of a daemon thread invoking exit
* after DestroyJavaVM initiates the shutdown sequence
*/
if (state != HOOKS) return;
}
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
}
它最终会调用runHooks方法,然后启动在上面静态块中添加的Runnable线程,最终启动所有已注册的钩子程序。
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
currentRunningHook = i;
hook = hooks[i];
}
//该run方法最终执行静态块中的runHooks()方法。
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
接下来找到Terminator
中:
class Terminator {
private static SignalHandler handler = null;
/* Invocations of setup and teardown are already synchronized
* on the shutdown lock, so no further synchronization is needed here
*/
static void setup() {
if (handler != null) return;
SignalHandler sh = new SignalHandler() {
public void handle(Signal sig) {
Shutdown.exit(sig.getNumber() + 0200);
}
};
handler = sh;
// When -Xrs is specified the user is responsible for
// ensuring that shutdown hooks are run by calling
// System.exit()
//这就是响应中断的方法
try {
Signal.handle(new Signal("INT"), sh);
} catch (IllegalArgumentException e) {
}
try {
//TERM对应15,即kill -9
Signal.handle(new Signal("TERM"), sh);
} catch (IllegalArgumentException e) {
}
}
其中Signal.handle()就是处理中断信号的方法,最终会通过sh回调Shutdown.exit()方法。最终触发sequence()方法被调用,然后调用所有注册的钩子程序。
五、最后
初步介绍了一下PreDestroy的原理和钩子程序的一些细节,由于标题只是讲PreDestroy,所以其中省略了不少spring的实现细节。谢谢大家~
相关推荐
spring bean 的生命周期,把运行结果的日志,用sublime打开对比查看,你会有比较清晰的认识
Spring bean生命周期demo
主要给大家介绍了Spring中Bean的生命周期和作用域及实现方式的相关资料,文中介绍的非常详细,对大家具有一定的参考价值,需要的朋友们下面来一起看看吧。
Springbean生命周期
spring的创建详解图片,仅供参考
spring ioc指的是控制反转,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。交由Spring容器统一进行管理,从而实现松耦合
Spring生命周期.vsdx
你不仅能从木书中参透Spring框架的优秀架构和设计思想,而且还能从Spring优雅的实现源码中一窥Java语言的精髓。此外,《Spring技术内幕:深入解析Spring架构与设计原理》还展示了阅读源代码的卓越方法,不仅授你以鱼...
NULL 博文链接:https://zhang-yingjie-qq-com.iteye.com/blog/319927
这个工程主要实现了: Spring中Bean的生命周期 applicationcontext的应用(实现国际化,事件的传递)
SpringBean的生命周期.mdj
此资源是我的博客bean的生命周期的测试代码,只有源代码,没有相关库文件,环境是spring4.2 ,
你不仅能从本书中参透Spring框架的优秀架构和设计思想,还能从Spring优雅的实现源码中一窥Java语言的精髓。本书在开篇之前对Spring的设计理念和整体架构进行了全面的介绍,能让读者从宏观上厘清Spring各个功能模块...
Spring实现原理、IoC容器的优点及在Eclipse 中创建Spring的 Web应用
这是对Spring中注解是怎么实现的一个大概基本原理,条件是采取的理想状态,所以代码中还有缺陷的话请谅解,如果有需要的朋友可以放心下载,里面有详细的解释和流程。相信你能看懂
第二十章:Spring 应用上下文生命周期小马哥 · mercyblitzSpring 应用上下文生命周期Spring 应用上下文启动准备阶段BeanFacto
你不仅能从本书中参透Spring框架的出色架构和设计思想,还能从Spring优雅的实现源码中一窥Java语言的精髓。本书在开篇之前对Spring的设计理念和整体架构进行了全面的介绍,能让读者从宏观上厘清Spring各个功能模块...
Spring管理的Bean的生命周期
Spring框架系列(8) - Spring IOC实现原理详解之Bean实例化(生命周期,循环依赖等).doc
学习Spring过程中,使用Eclipse调试Spring源码的关键断点文件。