chapter07_初始化和销毁方法

一、简介

一个Bean,在进行实例化之后,需要进行两种初始化

  • 初始化属性,由PropertyValues进行赋值
  • 初始化方法,由ApplicationContext统一调用,例如加载配置文件

Bean的初始化与销毁,共有三种方式(注解、接口、XML),本章节,只实现接口和XML

  • @PostConstruct@PreDestroy 注解是比较推荐的方式。
  • InitializingBeanDisposableBean 是实现接口方式,比较少用。
  • initMethoddestroyMethod 适用于 XML 配置。

二、初始化方法

2.1 基于接口的实现

定义初始化接口

public interface InitializingBean {/*** Bean 处理了属性填充后调用** @throws Exception*/void afterPropertiesSet();
}

定义销毁接口

public interface DisposableBean {void destroy();
}

2.2 基于XML的实现

给BeanDefinition新增初始化和销毁属性

  • 记录XML里面配置的初始化和销毁方法名称
@Data
public class BeanDefinition {······private String initMethodName;private String destroyMethodName;······
}

修改解析XML的逻辑

  • 修改XmlBeanDefinitionReader 类的doLoadBeanDefinitions 方法
  • 增加对init-method、destroy-method标签的读取
  • 并保存到BeanDefinition中
private void doLoadBeanDefinitions(InputStream inputStream) {Document doc = XmlUtil.readXML(inputStream);Element root = doc.getDocumentElement();NodeList childNodes = root.getChildNodes();for (int i = 0; i < childNodes.getLength(); i++) {// 判断元素if (!(childNodes.item(i) instanceof Element)) continue;// 判断对象if (!"bean".equals(childNodes.item(i).getNodeName())) continue;// 解析标签Element bean = (Element) childNodes.item(i);String id = bean.getAttribute("id");String name = bean.getAttribute("name");String className = bean.getAttribute("class");//增加对init-method、destroy-method的读取String initMethod = bean.getAttribute("init-method");String destroyMethodName = bean.getAttribute("destroy-method");// 获取 Class,方便获取类中的名称Class<?> clazz = null;try {clazz = Class.forName(className);} catch (ClassNotFoundException e) {throw new RuntimeException("不存在的类名" + className);}// 优先级 id > name,此处是Bean自己的id和nameString beanName = StrUtil.isNotEmpty(id) ? id : name;if (StrUtil.isEmpty(beanName)) {beanName = StrUtil.lowerFirst(clazz.getSimpleName());}// 定义BeanBeanDefinition beanDefinition = new BeanDefinition(clazz);//额外设置到beanDefinition中beanDefinition.setInitMethodName(initMethod);beanDefinition.setDestroyMethodName(destroyMethodName);// 读取属性并填充for (int j = 0; j < bean.getChildNodes().getLength(); j++) {if (!(bean.getChildNodes().item(j) instanceof Element)) continue;if (!"property".equals(bean.getChildNodes().item(j).getNodeName())) continue;// 解析标签:propertyElement property = (Element) bean.getChildNodes().item(j);String attrName = property.getAttribute("name");String attrValue = property.getAttribute("value");String attrRef = property.getAttribute("ref");// 获取属性值:引入对象、值对象Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;// 创建属性信息PropertyValue propertyValue = new PropertyValue(attrName, value);beanDefinition.getPropertyValues().addPropertyValue(propertyValue);}if (getRegistry().containsBeanDefinition(beanName)) {throw new RuntimeException("Duplicate beanName[" + beanName + "] is not allowed");}// 注册 BeanDefinitiongetRegistry().registerBeanDefinition(beanName, beanDefinition);}}

2.3 始化方法调用的时机

  • 位于AbstractAutowireCapableBeanFactory 类中
protected void invokeInitMethods(String beanName, Object bean, BeanDefinition beanDefinition) {// 1.是否实现了InitializingBean接口if (bean instanceof InitializingBean) {((InitializingBean) bean).afterPropertiesSet();}// 2.是否xml中配置了String initMethodName = beanDefinition.getInitMethodName();if (StrUtil.isNotBlank(initMethodName)) {try {Method initMethod = beanDefinition.getBeanClass().getMethod(initMethodName);initMethod.invoke(bean);} catch (Exception e) {throw new RuntimeException("Could not find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'");}}
}

三、销毁方法

3.1 适配器模式实现销毁接口

由于销毁方法也有多种配置方式,接口、XML、注解,使用适配器模式将Bean包装,交给Spring调用

  • 将实现了销毁方法的Bean,统一包装成DisposableBeanAdapter
  • destroy方法可能会调用两次,XML里面销毁方法配置成destroy,同时又实现DisposableBean接口,所以使用适配器模式重写了destroy方法,保证只调用一次
public class DisposableBeanAdapter implements DisposableBean {private final Object bean;private final String beanName;private String destroyMethodName;public DisposableBeanAdapter(Object bean, String beanName, BeanDefinition beanDefinition) {this.bean = bean;this.beanName = beanName;this.destroyMethodName = beanDefinition.getDestroyMethodName();}@Overridepublic void destroy() {// 1.实现接口 DisposableBeanif (bean instanceof DisposableBean) {((DisposableBean) bean).destroy();}// 2.避免同时继承自DisposableBean,且自定义方法与DisposableBean方法同名,销毁方法执行两次的情况if (StrUtil.isNotEmpty(destroyMethodName) && !(bean instanceof DisposableBean && "destroy".equals(this.destroyMethodName))) {try {Method destroyMethod = bean.getClass().getMethod(destroyMethodName);destroyMethod.invoke(bean);} catch (Exception e) {throw new RuntimeException("Couldn't find a destroy method named '" + destroyMethodName + "' on bean with name '" + beanName + "'");}}}
}

3.2 让DefaultSingletonBeanRegistry管理可销毁的Bean

DefaultSingletonBeanRegistry 类,新增一个disposableBeans属性,保存可销毁的Bean

  • 注意这里保存的是经过适配器模式包装的DisposableBean,重写了统一的destroy方法
  • 这里实现了destroySingletons方法,这个方法由ConfigurableBeanFactory接口定义
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {......private final Map<String, DisposableBean> disposableBeans = new HashMap<>();......public void registerDisposableBean(String beanName, DisposableBean bean) {disposableBeans.put(beanName, bean);}public void destroySingletons() {Set<String> beanNames = disposableBeans.keySet();for (String beanName : beanNames) {DisposableBean disposableBean = disposableBeans.get(beanName);try {disposableBean.destroy();} catch (Exception e) {throw new RuntimeException("Destroy method on bean with name '" + beanName + "' threw an exception", e);}}disposableBeans.clear();}
}

ConfigurableBeanFactory接口定义销毁Bean的方法

  • 这个方法会在虚拟机关闭的统一调用
  • AbstractBeanFactory 实现了ConfigurableBeanFactory 接口,但具体实现却交给了父类DefaultSingletonBeanRegistry ,这是因为父类的功能就是管理单例Bean的,非常合理的设计(子类实现了接口,但具体的实现写在了父类)
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory {/*** @param beanPostProcessor*/void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);/*** 销毁单例bean*/void destroySingletons();
}

3.3 创建Bean的时候保存销毁方法

销毁方法会在BeanFactory关闭的时候调用,所以在Bean创建的时候,先进行保存

  • 仍然是修改AbstractAutowireCapableBeanFactory
@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) {//实例化,包括构造函数注入Object bean = doCreateBean(beanName, beanDefinition, args);//依赖注入populateBean(beanName, bean, beanDefinition);//初始化bean = initializeBean(beanName, bean, beanDefinition);// 注册实现了 DisposableBean 接口的 Bean 对象registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);//加入单例池addSingleton(beanName, bean);return bean;
}protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));}
}

3.4 调用销毁Bean的方法

由于销毁bean会在虚拟机关闭的时候调用,先扩展一下ConfigurableApplicationContext

  • 新增registerShutdownHook 方法
  • 新增close方法
public interface ConfigurableApplicationContext extends ApplicationContext {void refresh();void registerShutdownHook();void close();
}

AbstractApplicationContext 中实现对应的方法

  • 虚拟机关闭的时候会调用注册到hook里面的方法
  • 进而调用close方法
@Override
public void registerShutdownHook() {Runtime.getRuntime().addShutdownHook(new Thread(this::close));
}@Override
public void close() {getBeanFactory().destroySingletons();
}

四、测试

Cat类

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Cat {private String name;private int weight;
}

Person类

@Slf4j
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Person {private String name;private int age;private Cat cat;public void initDataMethod(){log.info("执行Person:init-method");}public void destroyDataMethod(){log.info("执行Person:destroy-method");}
}

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="cat" class="cn.shopifymall.springframework.test.bean.Cat"><property name="name" value="tomcat"/><property name="weight" value="2000"/></bean><bean id="person" class="cn.shopifymall.springframework.test.bean.Person" init-method="initDataMethod"destroy-method="destroyDataMethod"><property name="name" value="LeBron James"/><property name="age" value="18"/><property name="cat" ref="cat"/></bean></beans>

测试类

public class ApiTest {@Testpublic void test_xml() {// 1.初始化 BeanFactoryClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");applicationContext.registerShutdownHook();// 2. 获取Bean对象调用方法Person person = (Person) applicationContext.getBean("person");System.out.println("测试结果:" + person);}
}

打印输出

  • 记住要看这个测试类的日志,不是方法的日志,因为虚拟机运行结束的日志在测试类里
  • 可以看到destroy-method打印
Connected to the target VM, address: '127.0.0.1:56254', transport: 'socket'
23:26:38.967 [main] INFO cn.shopifymall.springframework.test.bean.Person - 执行Person:init-method
测试结果:Person(name=LeBron James, age=18, cat=Cat(name=tomcat, weight=2000))
23:26:38.984 [Thread-0] INFO cn.shopifymall.springframework.test.bean.Person - 执行Person:destroy-method
Disconnected from the target VM, address: '127.0.0.1:56254', transport: 'socket'Process finished with exit code 0

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

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

相关文章

open webui源码分析6-Function

一、Functions简介 可以把Tools作为依赖于外部服务的插件&#xff0c;Functions就是内部插件&#xff0c;二者都是用来增强open webui的能力的。Functions是轻量的&#xff0c;高度可定制的&#xff0c;并且是用纯Python编写的&#xff0c;所以你可以自由地创建任何东西——从新…

C2039 “unref“:不是“osgEarth::Symbology::Style”的成员 问题分析及解决方法

在osgEarth2.10中实现多线段连续测量功能时,遇到下图中的错误; 经过测试和验证,主要问题出现在下图圈出代码的定义上 图22-1 对于22-1中的两个变量这样定义是错误的。因为Style类没有继承自osg::Referenced,因此不能与osg::ref_ptr配合使用

GitHub 热榜项目 - 日榜(2025-08-19)

GitHub 热榜项目 - 日榜(2025-08-19) 生成于&#xff1a;2025-08-19 统计摘要 共发现热门项目&#xff1a;12 个 榜单类型&#xff1a;日榜 本期热点趋势总结 本期GitHub热榜呈现三大技术热点&#xff1a;1&#xff09;AI原生开发持续爆发&#xff0c;Archon OS、Parlant等…

ingress 配置ssl证书

模拟环境举例&#xff1a; # 生成带 OU 的证书配置文件 cat > csr.conf <<EOF [ req ] default_bits 2048 prompt no default_md sha256 distinguished_name dn[ dn ] C CN ST Beijing L Beijing O YourCompany, Inc. # 组织名称 (必填) OU DevOps De…

Pandas 合并数据集:concat 和 append

文章目录Pandas 合并数据集&#xff1a;concat 和 append回顾&#xff1a;NumPy 数组的拼接使用 pd.concat 进行简单拼接重复索引将重复索引视为错误忽略索引添加多级索引&#xff08;MultiIndex&#xff09;键使用连接&#xff08;Join&#xff09;方式拼接append 方法Pandas …

2025年5月架构设计师综合知识真题回顾,附参考答案、解析及所涉知识点(七)

本文主要回顾2025年上半年(2025-5-24)系统架构设计师考试上午综合知识科目的选择题,同时附带参考答案、解析和所涉知识点。 2025年5月架构设计师综合知识真题回顾,附参考答案、解析及所涉知识点(一) 2025年5月架构设计师综合知识真题回顾,附参考答案、解析及所涉知识点(…

面向RF设计人员的微带贴片天线计算器

微带贴片天线和阵列可能是仅次于单极天线和偶极天线的最简单的天线设计。这些天线也很容易集成到PCB中&#xff0c;因此通常用于5G天线阵列和雷达等高级系统。这些天线阵列在基谐模式和高阶模式下也遵循一组简单的设计方程&#xff0c;因此您甚至可以在不使用仿真工具的情况下设…

明基RD280U编程显示器深度测评:码农的「第二块键盘」竟然会发光?

文章目录前言一、开箱篇&#xff1a;当理工男遇到「俄罗斯套娃式包装」二、外观篇&#xff1a;深空灰的「代码容器」1. 桌面变形记2. 保护肩颈的人体工学设计三、显示篇&#xff1a;给代码做「光子嫩肤」1. 28寸超大大屏 3:2屏比 4K超清2.专业编程模式&#xff0c;让代码一目…

算法114. 二叉树展开为链表

题目&#xff1a;给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序遍历 顺序相同。…

智慧能源管理系统:点亮山东零碳园区的绿色引擎

一、概述在全球积极践行“双碳”目标的时代浪潮下&#xff0c;山东作为经济大省&#xff0c;正全力推动产业的绿色变革&#xff0c;零碳园区建设成为其中的关键一环。《山东省零碳园区建设方案》明确规划&#xff0c;到2027年建成15个左右省级零碳园区 &#xff0c;到2030年进一…

分布式日志分析平台(ELFK 与 EFK)理论

一、日志分析平台核心概念在分布式系统中&#xff0c;日志是系统运行状态监控、问题排查和业务分析的重要依据。随着系统规模扩大&#xff0c;单机日志管理方式已无法满足需求&#xff0c;分布式日志分析平台应运而生。其核心目标是实现日志的集中收集、统一处理、高效存储和可…

CoreShop微信小程序商城框架开启多租户-添加一个WPF客户端以便进行本地操作--读取店铺信息(6)

本节内容&#xff0c;使用登录的token进行店铺信息读取&#xff0c;顺利的话&#xff0c;进行EXCEL上传测试。 1。在后台编写 读取店铺信息代码 1.1 查看原来铺店信息在什么位置&#xff0c;店铺的表格为CoreCmsStore#region 获取列表// POST: Api/CoreCmsStore/GetPageList///…

UE5关卡蓝图能不能保存副本呀?

提问 关卡蓝图能不能保存副本呀&#xff1f; 回答 在 UE 里&#xff0c;“关卡蓝图&#xff08;Level Blueprint&#xff09;”本身其实是不能直接复制/保存成独立资源的&#xff0c;因为它和具体的 **Level&#xff08;.umap 文件&#xff09;**是绑定的——相当于一个“场景脚…

机器学习数据预处理学习报告

一、学习背景与目的在机器学习流程中&#xff0c;数据预处理是保障模型训练效果的关键环节。原始数据常存在缺失值、量纲不一致、特征格式不匹配等问题&#xff0c;直接影响模型对数据规律的学习。本次学习围绕 Pandas 与 Scikit-learn&#xff08;sklearn&#xff09;工具库&a…

git旧仓库迁移到新仓库

git旧仓库迁移到新仓库 A仓库(旧仓库)&#xff1a;git172.16.21.21:xxxx_software/Ni-Handler-Mgr.git B仓库(新仓库)&#xff1a;git172.16.11.11:yyyy/hostpc/ni-handler-mgr.git Step1 新建新仓库 创建新 GitHub 仓库‌ 在 GitHub 页面点击 “New repository”&#xff0c;命…

YOLO --- YOLOv5模型以及项目详解

YOLO — YOLOv5模型以及项目详解 文章目录YOLO --- YOLOv5模型以及项目详解一&#xff0c;开源地址二&#xff0c;改进点Focus 模块三&#xff0c;网络结构3.1 CSP1_X 与 CSP2_X3.2 自适应Anchor的计算3.3 激活函数3.3.1 SiLU3.3.2 Swish3.4 Bottleneck3.5 C33.5.1 BottleneckC…

Linux文本三剑客的使用及常见重点操作

文本三剑客指 Linux环境下的 grep&#xff08;搜索&#xff09;、sed&#xff08;编辑&#xff09;、awk&#xff08;分析&#xff09;三款用于文本处理的核心命令&#xff0c;三者分工明确、功能互补&#xff0c;是处理日志、配置文件、结构化数据等场景的 “刚需工具”。一、…

​​《开源字幕神器VideoCaptioner实战:基于Whisper+LLM的全链路方案,免费平替剪映会员》​​

&#x1f4cc; 大家好&#xff0c;我是智界工具库&#xff0c;每天分享好用实用且智能的开源项目&#xff0c;以及在JAVA语言开发中遇到的问题&#xff0c;如果本篇文章对您有所帮助&#xff0c;请帮我点个小赞小收藏小关注吧&#xff0c;谢谢喲&#xff01;&#x1f618; 博主…

redisIO模型

​​1. 总述核心​​“Redis采用了​​单线程的Reactor模型​​来处理网络IO和命令请求。其核心在于&#xff0c;​​它使用一个主线程通过IO多路复用机制来并发地处理大量的客户端连接&#xff0c;而实际的命令解析和执行则是单线程的​​。”这句话非常重要&#xff0c;它直接…

视觉采集模块的用法

一、图像源模块用法采集模块中最基础的单元就是图像源模块&#xff0c;其中图像的输入方式包括相机输入、本地图像、SDK三种。添加图像源后&#xff0c;需要对内部的参数进行对应的配置&#xff0c;正常我们连接相机后图像源选择我们对应的连接相机。配置所需要的相机参数&…