一、引言

在开发中我们如果要在关闭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是实现了BeanFactoryAwareBeanFactoryAware的,也就是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() {@Overridepublic 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 -9Signal.handle(new Signal("TERM"), sh);} catch (IllegalArgumentException e) {}}

其中Signal.handle()就是处理中断信号的方法,最终会通过sh回调Shutdown.exit()方法。最终触发sequence()方法被调用,然后调用所有注册的钩子程序。

五、最后

初步介绍了一下PreDestroy的原理和钩子程序的一些细节,由于标题只是讲PreDestroy,所以其中省略了不少spring的实现细节。谢谢大家~

spring注解之@PreDestroy的实现原理相关推荐

  1. spring注解驱动开发-8 Spring 扩展原理

    Spring 扩展原理 前言 BeanFactoryPostProcessor 测试实例编写 ExtConfig MyBeanFactoryPostProcessor ExtTest 源码分析 Bea ...

  2. spring注解驱动开发-6 Spring AOP实现原理

    Spring AOP实现原理 前言 1.@EnableAspectJAutoProxy注解原理 2.AnnotationAwareAspectJAutoProxyCreator 分析 1.分析前工作, ...

  3. Spring注解解析及工作原理、自定义注解

    注解(Annotation) 提供了一种安全的类似注释的机制,为我们在代码中添加信息提供了一种形式化得方法,使我们可以在稍后某个时刻方便的使用这些数据(通过解析注解来使用这些 数据),用来将任何的信息 ...

  4. 0、Spring 注解驱动开发

    0.Spring注解驱动开发 0.1 简介 <Spring注解驱动开发>是一套帮助我们深入了解Spring原理机制的教程: 现今SpringBoot.SpringCloud技术非常火热,作 ...

  5. Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  6. Spring 注解教程

    Spring 注解教程 Spring Annotations允许我们通过java程序配置依赖项并实现依赖项注入. 目录[ 隐藏 ] 1 Spring注释 1.1 Spring注释列表 1.2 Spri ...

  7. 第二章 ---- spring注解开发

    文章目录 参考视频 注解驱动的意义 常用注解(重点) 启动注解驱动 IoC bean定义(@Component .@Controller.@Service. @Repository) @Scope b ...

  8. 设计模式——Spring注解编程模型

    文章目录 1. 引言 2. Spring注解编程模型 2.1 元注解(Meta-Annotations) 2.2 Spring模式注解(Stereotype Annotations) 2.3 Spri ...

  9. Spring注解大全(史上最全,字母编号,有实例)

    目录 A @Accessors 链式编程使用.需要搭配@Getter和@Setter使用.主要有三个参数: 序号 参数名 介绍 1 chain 链式 2 fluent 流式(若无显示指定chain的值 ...

最新文章

  1. body标签下莫名奇妙多了一行空行,原来是编码的问题
  2. 循环神经网络 递归神经网络_了解递归神经网络中的注意力
  3. 提交MTBF eservice以及log注意事项
  4. ubuntu16.04+cuda9.0_cudnn7.5+tensorflow-gpu==1.12.0
  5. vue中router-link绑定click失效
  6. ASCII码对照表(参考用)
  7. docker构建dpdk运行环境镜像
  8. python绘制动态心电图_可穿戴设备中测心电图这样功能能达到医用标准吗?未来前景如何?在医用和便携之间是否还有市场?...
  9. 刑事案件鉴定意见常用质证要点
  10. 自定义下拉回弹View-掌握View冲突处理
  11. 【读书笔记】信贷周期的产生
  12. 高数:微分中值定理介值定理证明题浅析
  13. LoRaWAN入网方式以及加密进阶版
  14. SSM人才交流平台的开发毕业设计-附源码
  15. Android 4.0 平台特性
  16. 扇形图形用html,css如何画扇形?
  17. HTML5简单个人主页设计
  18. pe备份linux系统教程,如何使用老毛桃winpe的Bootice工具备份SYSLINUX引导程序?
  19. 此计算机无法访问移动网络,IT教程:为什么打电话显示无法访问移动网络
  20. Java基础(顺序结构)学习笔记

热门文章

  1. 升级JSONB列式存储,Hologres助力淘宝搜索2022双11降本增效!
  2. 华为手机如何实现语音转文字?简单的很,一步步教你完成
  3. 转载:关于Vivado综合选项——Out of context per IP和Gobal
  4. 书写软件之钢笔笔迹实现(一)
  5. Jetpack Compose——Icon(图标)的使用
  6. android之基于百度语音合讯飞语音识别的语音交互
  7. Quartz - Java 任务调度
  8. 动态规划——背包问题九解(01背包)
  9. 在Ubuntu18.04 LTS下升级Python版本
  10. vue3 + TypeScript + vant +pinia 实现网易云音乐播放器