Spring中你一定要知道的@PostConstruct/@PreDestroy

文章目录

  • 功能
  • 源码
    • 解析
    • 执行

功能

Spring中存在很多回调,但是执行他们的时机都不相同,也许大家用的最多的是InitializingBean.afterPropertiesSet,这个方法的作用如名称一样,是bean初始化后执行的一个回调操作,而@PostConstructinitMethod,初始化回调方法,它是在afterPropertiesSet之前执行的,并且可以有多个@PostConstruct@PreDestory,所以它的功能会更强大一点。

但需要注意的是,多个@PostConstruct方法的顺序是:父类内从先到后,然后子类内从先到后,多个@PreDestory方法的顺序是:子类内从先到后,父类内从先到后。

另外一个点:这里我说它是initMethod是因为Spring本身对这个方法解析后的命名就叫initMethod,保存在InitDestroyAnnotationBeanPostProcessor中,真正意义上的initMethod是保存在BeanDefinition里的,并且只有一个,所以这个要区分一下。

如下示例:

@Component
public class CustomConfig7 {@PostConstructpublic void t() {System.out.println("customConfig7 init");}@PostConstructpublic void t2() {System.out.println("customConfig7 init2");}@PreDestroypublic void d() {System.out.println("customConfig7 destroy");}@PreDestroypublic void d2() {System.out.println("customConfig7 destroy2");}
}

源码

它的解析和执行过程比较简单,很快就可以看完。

解析

位置:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

image-20231221214221147

这里它是调用了父类的方法postProcessMergedBeanDefinition进行解析的, 下面findResourceMetadata是依赖注入的注入点解析。

private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {// lifecycleMetadataCache 初始化时赋了初始值,所以这里不会走if (this.lifecycleMetadataCache == null) {return buildLifecycleMetadata(clazz);}// 从缓存种获取// 双重判断LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);if (metadata == null) {synchronized (this.lifecycleMetadataCache) {metadata = this.lifecycleMetadataCache.get(clazz);if (metadata == null) {// 解析classmetadata = buildLifecycleMetadata(clazz);this.lifecycleMetadataCache.put(clazz, metadata);}return metadata;}}return metadata;}

这里会看到synchronized,Spring创建bean,也就是bean的生命周期这个过程时单线程的,但它这里加了锁,这个问题,作为底层框架要保证线程安全,因为在业务场景下,并不都是单线程的,比如:

你定义了一个bean,简称A,同时你实现了接口InitializingBean,在回调方法时,你开启了一个线程B,这个线程B可能做数据查询,数据缓存等,那么他需要redis,datasource,sqlSession这些,那么它也是从beanFactory获取bean,这时Spring也在getBean,线程B和Spring就已经并行了,就是应用本身是多线程的,作为底层的框架需要保证线程安全。

那接下来,我们看一下构建方法:

位置:org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#buildLifecycleMetadata

private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {// 进行过滤// initAnnotationType -> @PostConstruct// destroyAnnotationType -> @PreDestroyif (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {return this.emptyLifecycleMetadata;}List<LifecycleElement> initMethods = new ArrayList<>();List<LifecycleElement> destroyMethods = new ArrayList<>();Class<?> targetClass = clazz;do {final List<LifecycleElement> currInitMethods = new ArrayList<>();final List<LifecycleElement> currDestroyMethods = new ArrayList<>();
// Spring的反射工具,解析并遍历class里的方法ReflectionUtils.doWithLocalMethods(targetClass, method -> {// 如果方法含有注解:@PostConstruct,就添加到currInitMethodsif (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {LifecycleElement element = new LifecycleElement(method);currInitMethods.add(element);if (logger.isTraceEnabled()) {logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);}}// 如果方法含有注解:@PreDestroy,就添加到currDestroyMethodsif (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {currDestroyMethods.add(new LifecycleElement(method));if (logger.isTraceEnabled()) {logger.trace("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 (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata :new LifecycleMetadata(clazz, initMethods, destroyMethods));}

注意: 上面它是一个向上的循环,先顺序遍历当前类,再找父类,一直往上找,而有一点不一样:

initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);

initMethod每次都放第一个,所以,父类的@PostConstruct应该在子类之前执行,这个也和类的实例是一样的顺序。

destroyMethod是顺序放入,执行顺序应该是子类先执行;

这个执行顺序,这里只是根据它放入的顺序的一个猜测,最终顺序还得看它执行的过程。

执行

在初始化bean时,spring执行了一个后置处理器,而执行@PostConstruct/@PreDestroy就是在里面执行的

image-20231221215959070

位置:org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization

image-20231221220411186

LifecycleMetadata是在解析时生成的对象,看一下它的属性:

image-20231221221155957

它的执行如下,是通过遍历的方式,所以@PonstConstruct执行的顺序是先执行父类的,然后执行子类的。

image-20231221222554761

至于@PreDestroy,它是在Spring容器关闭时执行的,有ApplicationContext触发,位置:

org.springframework.context.support.AbstractApplicationContext#doClose

同样最终执行的位置和@PostConstruct是同一个处理器,两个的代码几乎是一样的,都是直接遍历解析号的方法集,然后顺序执行,不过,这里先执行的是子类,然后才是父类的。

image-20231221223708606

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/239073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MarkDown阅读器

MarkDown阅读器 MarkText MarkText MarkText&#xff1a;https://github.com/marktext/marktext 或 https://www.marktext.cc

Vue router-view 组件如何实现回调处理

在 Vue.js 中&#xff0c;router-view 组件是用于展示由 vue-router 管理的路由匹配到的组件。router-view 本身并不直接提供回调函数功能&#xff0c;如页面加载完成后的事件回调。但是我吗可以通过其他方式来实现类似的功能&#xff1a; 监听路由变化&#xff1a; 你可以在父…

网络安全—四种攻击方式

四种攻击方式的概述 ARP攻击 概念 ARP协议的基本功能就是通过目标设备的IP地址&#xff0c;在局域网发送广播包&#xff0c;查询目标设备的MAC地址&#xff0c;以保证通信的进行。 ARP攻击 基于ARP协议的这一工作特性&#xff0c;黑客向对方计算机不断发送有欺诈性质的ARP数…

nodejs+vue+ElementUi家政服务系统c90g5

项目中登录模块用到token家政服务平台有管理员&#xff0c;雇主&#xff0c;雇员三个角色。管理员功能有个人中心&#xff0c;雇主管理&#xff0c;雇员管理&#xff0c;资料认证管理&#xff0c;项目类型管理&#xff0c;服务项目管理&#xff0c;需求信息管理&#xff0c;服务…

博客迁移通知

本博客停止更新&#xff0c;即将迁移到个人网站 http://120.24.170.168/ 本人一枚80后&#xff0c;从事JAVA软件开发10余年&#xff0c;掌握大数据和java技术以及相关的Linux运维技术&#xff0c;面临求职找工作&#xff0c;邮箱&#xff1a;904456849qq.com&#xff0c;感谢广…

C# WPF上位机开发(软件的发布和部署)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 软件编译完成之后&#xff0c;本地测试完之后&#xff0c;一般都要发布和部署到客户的电脑上面。客户电脑的配置未必和开发的电脑是一样的&#xf…

mysql 23day DDL常用约束,数据类型

目录 数据库的四种语言常用约束&#xff08;DDL&#xff09;创建格式PRIMARY KEY&#xff1a;主键约束FOREIGN KEY&#xff1a;外键约束主键外键 联合测试开始测试 NOT NULL&#xff1a;非空约束UNIQUE&#xff1a;唯一约束DEFAULT&#xff1a;默认值约束zerofill 零填充auto_i…

自制数据库空洞率清理工具-C版-01-EasyClean-V1.0(支持南大通用数据库Gbase8a)

目录 一、环境信息 二、简述 三、支持功能 四、空洞率 五、工具流程图 六、安装包下载地址 七、参数介绍 1、命令模板 2、命令样例 3、参数表格 八、安装步骤 1、配置环境变量 2、生效环境变量 3、检验动态链接是否正常 九、运行效果 一、环境信息 名称值CPUInt…

Windows电脑向ipad和iOS系统共享文件夹

Windows电脑向ipad和iOS系统共享文件夹 这个方案不需要下载任何软件&#xff0c;但是要求 iOS 和 Windows 在同一个局域网内。再大的文件都可以在 iOS13 自带的的“文件App”里实时显示&#xff0c;可以直接打开。这个解决方案需要你 Windows 电脑上登陆了微软账号&#xff0c…

原生JavaScript实现 元素全屏与退出全屏效果

之前写过 前端screenfull实现界面全屏展示功能 突然发现自己犯傻了 其实元素js中就有全屏与取消全屏的方式 html代码如下 <!DOCTYPE html> <html> <head><title>全屏实验</title><style></style> </head> <body><d…

数据结构考试测试编程题

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

小程序常用实用例子

拍照 // 选择照片 ChooseImage() { wx.chooseImage({ count: 4, //默认9 sizeType: [original, compressed], //可以指定是原图还是压缩图&#xff0c;默认二者都有 sourceType: [album], //从相册选择 success: (res) > { if (this.data.imgList.length ! 0) { this.setDa…

Qt不能在线程函数操作UI

Qt的官方文档虽然并没有明确说明&#xff0c;不能在子线程中操作UI&#xff0c;但是&#xff0c;因为Qt是事件循环机制&#xff0c;所有的UI事件都是在主线程中处理的&#xff0c;如果在子线程中直接操作UI&#xff0c;就会存在线程安全问题&#xff0c;严重时候会使程序挂掉。…

flutter开发实战-第一帧布局完成回调实现

flutter开发实战-第一帧布局完成回调实现 在开发中&#xff0c;我们有时候需要在第一帧布局完成后调用一些相关的方法。这里记录一下是实现过程。 Flutter中有多种不同的Binding&#xff0c;每种Binding都负责不同的功能。下面是Flutter中常见的Binding&#xff1a; 这里简单…

nginx userid到底做了啥?

我们公司在用nginx的userid模块作为简单的用户请求追踪使用。这个模块其实并不能真正记录用户的请求状态&#xff0c;只能作为一个辅助使用。但是在一些场景下会有一些异常。下面我们简单介绍一下这个模块到底做了什么。 userid 模块简介 官网说明文档 ngx_http_userid_modul…

Course Shell of Missing Semester(一)

〇、前言 本文是 The Missing Semester of Your CS Education 课程的课后题答案&#xff0c;课程网站点击这里&#xff0c;以后系列文章不再描述前言。 本文实验环境&#xff1a;阿里云Ubuntu 22.04 Course shell 1、本课程需要使用类Unix shell&#xff0c;例如 Bash 或 Z…

【三维生成与重建】ZeroRF:Zero Pretraining的快速稀疏视图360°重建

系列文章目录 题目&#xff1a;ZeroRF: Fast Sparse View 360◦ Reconstruction with Zero Pretraining 任务&#xff1a;稀疏重建&#xff1b;拓展&#xff1a;Image to 3D、文本到3D 作者&#xff1a;Ruoxi Shi* Xinyue Wei* Cheng Wang Hao Su &#xff0c;来自UC San Dieg…

c语言link

下面2个编译命令有何不同&#xff1a; 1. gcc a.o -L./ -lmm -o run 2. gcc -L./ -lmm a.o -o run a.o 依赖于libmm.a, 也就是说a.o 调用了libmm.a中的函数&#xff0c;今天在编码时&#xff0c;发现用第2种方式&#xff0c;链接时会报错&#xff0c;提示符号A没有定义, 但…

鸿蒙OS4.0开发学习路线以及注意点

学习鸿蒙OS 4.0开发需要掌握一系列的技能和知识&#xff0c;以下是一个较为详细的学习路线以及注意点&#xff1a; 基础知识准备 了解鸿蒙OS&#xff1a;首先需要对鸿蒙OS有一个基本的了解&#xff0c;包括其特点、架构和应用领域。 掌握Java和C编程语言&#xff1a;这两种编…

layui监听复选框checkbox选中,分页选中处理

具体实现代码如下&#xff08;需要关注三个位置&#xff1a;done表格加载完毕方法&#xff0c;cols中复选框栏定义&#xff0c;table.on中对复选框选中或取消的状态定义&#xff09;这三个地方的代码直接复制去用就行了 最终选中的数据id为&#xff1a;ids <script>layu…