一、目标

当我们的类创建的 Bean 对象,交给 Spring 容器管理以后,这个类对象就可以被赋予更多的使用能力。就像我们在上一章节已经给类对象添加了修改注册Bean定义未实例化前的属性信息修改和实例化过程中的前置和后置处理,这些额外能力的实现,都可以让我们对现有工程中的类对象做相应的扩展处理。那么除此之外我们还希望可以在 Bean 初始化过程,执行一些操作。比如帮我们做一些数据的加载执行,链接注册中心暴漏RPC接口以及在Web程序关闭时执行链接断开,内存销毁等操作。如果说没有Spring我们也可以通过构造函数、静态方法以及手动调用的方式实现,但这样的处理方式终究不如把诸如此类的操作都交给 Spring 容器来管理更加合适。 因此你会看到到 spring.xml 中有如下操作:

  1. 需要满足用户可以在 xml 中配置初始化和销毁的方法,也可以通过实现类的方式处理,比如我们在使用 Spring 时用到的
    InitializingBean, DisposableBean 两个接口。其实还可以有一种是注解的方式处理初始化操作,不过目前还没有实现到注解的逻辑,后续再完善此类功能。

二、设计

可能面对像 Spring 这样庞大的框架,对外暴露的接口定义使用或者xml配置,完成的一系列扩展性操作,都让 Spring 框架看上去很神秘。其实对于这样在 Bean 容器初始化过程中额外添加的处理操作,无非就是预先执行了一个定义好的接口方法或者是反射调用类中xml中配置的方法,最终你只要按照接口定义实现,就会有 Spring 容器在处理的过程中进行调用而已。整体设计结构如下图:

  1. 在 spring.xml 配置中添加 init-method 、 destroy-method 两个注解,在配置文件加载的过程中,把注解配置一并定义到 BeanDefinition 的属性当中。这样在 initializeBean初始化操作的工程中,就可以通过反射的方式来调用配置在 Bean 定义属性当中的方法信息了。另外如果是接口实现的方式,那么直接可以通过Bean对象调用对应接口定义的方法即可,((InitializingBean) bean).afterPropertiesSet()afterPropertiesSet(),两种方式达到的效果是一样的。
  2. 除了在初始化做的操作外, destroy-method 和 DisposableBean 接口的定义,都会在 Bean 对象初始化完成阶段,执行注册销毁方法的信息到 DefaultSingletonBeanRegistry 类中的 disposableBeans 属性里,这是为了后续统 一进行操作。这里还有一段适配器的使用,因为反射调用和接口直接调用,是两种 方式。所以需要使用适配器进行包装,下文代码讲解中参考 DisposableBeanAdapter 的具体实现 关于销毁方法需要在虚拟机执行关闭之前进行操作,所以这里需要用到一个注册钩子的操作,如:Runtime.getRuntime().addShutdownHook(new Thread(()-->System.out.println("close"))); 这段代码你可以执行测试 ,另外你可以使用手动调用 ApplicationContext.close 方法关闭容器。

三、实现

  1. 工程结构
small-spring-step-07
└── src├── main│   └── java│       └── cn.bugstack.springframework│           ├── beans│           │   ├── factory│           │   │   ├── config│           │   │   │   ├── AutowireCapableBeanFactory.java│           │   │   │   ├── BeanDefinition.java│           │   │   │   ├── BeanFactoryPostProcessor.java│           │   │   │   ├── BeanPostProcessor.java│           │   │   │   ├── BeanReference.java│           │   │   │   ├── ConfigurableBeanFactory.java│           │   │   │   └── SingletonBeanRegistry.java│           │   │   ├── support│           │   │   │   ├── AbstractAutowireCapableBeanFactory.java│           │   │   │   ├── AbstractBeanDefinitionReader.java│           │   │   │   ├── AbstractBeanFactory.java│           │   │   │   ├── BeanDefinitionReader.java│           │   │   │   ├── BeanDefinitionRegistry.java│           │   │   │   ├── CglibSubclassingInstantiationStrategy.java│           │   │   │   ├── DefaultListableBeanFactory.java│           │   │   │   ├── DefaultSingletonBeanRegistry.java│           │   │   │   ├── DisposableBeanAdapter.java│           │   │   │   ├── InstantiationStrategy.java│           │   │   │   └── SimpleInstantiationStrategy.java  │           │   │   ├── support│           │   │   │   └── XmlBeanDefinitionReader.java│           │   │   ├── BeanFactory.java│           │   │   ├── ConfigurableListableBeanFactory.java│           │   │   ├── DisposableBean.java│           │   │   ├── HierarchicalBeanFactory.java│           │   │   ├── InitializingBean.java│           │   │   └── ListableBeanFactory.java│           │   ├── BeansException.java│           │   ├── PropertyValue.java│           │   └── PropertyValues.java │           ├── context│           │   ├── support│           │   │   ├── AbstractApplicationContext.java │           │   │   ├── AbstractRefreshableApplicationContext.java │           │   │   ├── AbstractXmlApplicationContext.java │           │   │   └── ClassPathXmlApplicationContext.java │           │   ├── ApplicationContext.java │           │   └── ConfigurableApplicationContext.java│           ├── core.io│           │   ├── ClassPathResource.java │           │   ├── DefaultResourceLoader.java │           │   ├── FileSystemResource.java │           │   ├── Resource.java │           │   ├── ResourceLoader.java │           │   └── UrlResource.java│           └── utils│               └── ClassUtils.java└── test└── java└── cn.bugstack.springframework.test├── bean│   ├── UserDao.java│   └── UserService.java└── ApiTest.java
  1. Spring 应用上下文和对Bean对象扩展机制的类关系
  1. 以上整个类图结构描述出来的就是本次新增 Bean 实例化过程中的初始化方法和销毁方法。
  2. 因为我们一共实现了两种方式的初始化和销毁方法, xml 配置和定义接口,所以这里既有 InitializingBean 、 DisposableBean 也有需要 XmlBeanDefinitionReader 加载 spring.xml 配置信息到 BeanDefinition 中。
  3. 另外接口 ConfigurableBeanFactory 定义了 destroySingletons 销毁方法,并由AbstractBeanFactory 继承的父类 DefaultSingletonBeanRegistry 实现ConfigurableBeanFactory 接口定义的 destroySingletons 方法。 这种方式的设计可能是程序员是没有用过的,都是用的谁实现接口谁完成实现类,而不是把实现接口的操作又交给继承的父类处理。所以这块还是蛮有意思的,是一种不错的隔离分层的操作又交给继承的父类处理。
  4. 最后就是关于向虚拟机注册钩子,保证在虚拟机关闭之前,执行销毁操作。最后就是关于向虚拟机注册钩子,保证在虚拟机关闭之前,执行销毁操作。Runtime.getRuntime().addShutdownHook(new Thread(() Runtime.getRuntime().addShutdownHook(new Thread(() --> > System.out.println("closeSystem.out.println("close!!")));")));
  1. 定义初始化和销毁方法的接口
public interface InitializingBean {/*** Bean 处理了属性填充后调用* @throws Exception*/void afterPropertiesSet() throws Exception;
}
public interface DisposableBean {void destroy() throws Exception;
}
  1. InitializingBean 、 DisposableBean ,两个接口方法还是比较常用的,在一些需要结合 Spring 实现的组件中,经常会使用这两个方法来做一些参数的初始化和销毁操作。比如接口暴露、数据库数据读取、配置文件加载等等。
  1. Bean 属性定义新增初始化和销毁
public class BeanDefinition {private Class beanClass;private PropertyValues propertyValues;private String initMethodName;private String destroyMethodName;public BeanDefinition(Class beanClass) {this.beanClass = beanClass;this.propertyValues = new PropertyValues();}public BeanDefinition(Class beanClass, PropertyValues propertyValues) {this.beanClass = beanClass;this.propertyValues = propertyValues != null ? propertyValues : new PropertyValues();}public Class getBeanClass() {return beanClass;}public void setBeanClass(Class beanClass) {this.beanClass = beanClass;}public PropertyValues getPropertyValues() {return propertyValues;}public void setPropertyValues(PropertyValues propertyValues) {this.propertyValues = propertyValues;}public String getInitMethodName() {return initMethodName;}public void setInitMethodName(String initMethodName) {this.initMethodName = initMethodName;}public String getDestroyMethodName() {return destroyMethodName;}public void setDestroyMethodName(String destroyMethodName) {this.destroyMethodName = destroyMethodName;}
}
  1. 在 BeanDefinition 新增加了两个属性: initMethodName 、 destroyMethodName这两个属性是为了在 spring.xml 配置的 Bean 对象中,可以配置 init-method="initDataMethod" destroy-method="destroyDataMethod" 操作,最终实现接口的效果是一样的。 只不过一个是接口方法的直接调用,另外是一个在配置文件中读取到方法反射调用
  1. 执行 Bean 对象的初始化方法
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();@Overrideprotected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {Object bean = null;try {bean = createBeanInstance(beanDefinition, beanName, args);// 给 Bean 填充属性applyPropertyValues(beanName, bean, beanDefinition);// 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法bean = initializeBean(beanName, bean, beanDefinition);} catch (Exception e) {throw new BeansException("Instantiation of bean failed", e);}// ...addSingleton(beanName, bean);return bean;}private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) {// 1. 执行 BeanPostProcessor Before 处理Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);// 执行 Bean 对象的初始化方法try {invokeInitMethods(beanName, wrappedBean, beanDefinition);} catch (Exception e) {throw new BeansException("Invocation of init method of bean[" + beanName + "] failed", e);}// 2. 执行 BeanPostProcessor After 处理wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName);return wrappedBean;}private void invokeInitMethods(String beanName, Object bean, BeanDefinition beanDefinition) throws Exception {// 1. 实现接口 InitializingBeanif (bean instanceof InitializingBean) {((InitializingBean) bean).afterPropertiesSet();}// 2. 配置信息 init-method {判断是为了避免二次执行销毁}String initMethodName = beanDefinition.getInitMethodName();if (StrUtil.isNotEmpty(initMethodName)) {Method initMethod = beanDefinition.getBeanClass().getMethod(initMethodName);if (null == initMethod) {throw new BeansException("Could not find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'");}initMethod.invoke(bean);}}
}
  1. 抽象类 AbstractAutowireCapableBeanFactory 中的 createBean 是用来创建 Bean 对象的方法,在这个方法中我们之前已经扩展了 BeanFactoryPostProcessor、BeanPostProcessor 操作,这里我们继续完善执行 Bean 对象的初始化方法的处理动作。
  2. 在方法 invokeInitMethods 中,主要分为两块来执行实现了 InitializingBean 接口的操作,处理 afterPropertiesSet 方法。另外一个是判断配置信息 init-method 是否存在,执行反射调用 initMethod.invoke(bean)。这两种方式都可以在 Bean 对象初始化过程中进行处理加载 Bean 对象中的初始化操作,让使用者可以额外新增加自己想要的动作。
  1. 定义销毁方法适配器(接口和配置)
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() throws Exception {// 1. 实现接口 DisposableBeanif (bean instanceof DisposableBean) {((DisposableBean) bean).destroy();}// 2. 配置信息 destroy-method {判断是为了避免二次执行销毁}if (StrUtil.isNotEmpty(destroyMethodName) && !(bean instanceof DisposableBean && "destroy".equals(this.destroyMethodName))) {Method destroyMethod = bean.getClass().getMethod(destroyMethodName);if (null == destroyMethod) {throw new BeansException("Couldn't find a destroy method named '" + destroyMethodName + "' on bean with name '" + beanName + "'");}destroyMethod.invoke(bean);}}
}
  1. 可能你会想这里怎么有一个适配器的类呢,因为销毁方法有两种甚至多种方式,目前有实现接口 DisposableBean、配置信息 destroy-method,两种方式。而这两种方式的销毁动作是由 AbstractApplicationContext 在注册虚拟机钩子后看,虚拟机关闭前执行的操作动作。
  2. 那么在销毁执行时不太希望还得关注都销毁那些类型的方法,它的使用上更希望是有一个统一的接口进行销毁,所以这里就新增了适配类,做统一处理。
  1. 创建Bean时注册销毁方法对象
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();@Overrideprotected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {Object bean = null;try {bean = createBeanInstance(beanDefinition, beanName, args);// 给 Bean 填充属性applyPropertyValues(beanName, bean, beanDefinition);// 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法bean = initializeBean(beanName, bean, beanDefinition);} catch (Exception e) {throw new BeansException("Instantiation of bean failed", e);}// 注册实现了 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));}}
}
  1. 在创建 Bean 对象的实例的时候,需要把销毁方法保存起来,方便后续执行销毁动作进行调用
  2. 那么这个销毁方法的具体方法信息,会被注册到 DefaultSingletonBeanRegistry 中新增加的 Map<String, DisposableBean> disposableBeans 属性中去,因为这个接口的方法最终可以被类 AbstractApplicationContext 的 close 方法通过 getBeanFactory().destroySingletons() 调用
  3. 在注册销毁方法的时候,会根据是接口类型和配置类型统一交给 DisposableBeanAdapter 销毁适配器类来做统一处理。实现了某个接口的类可以被 instanceof 判断或者强转后调用接口方法
  1. 虚拟机关闭钩子注册调用销毁方法
public interface ConfigurableApplicationContext extends ApplicationContext {void refresh() throws BeansException;void registerShutdownHook();void close();
}
  1. 首先我们需要在 ConfigurableApplicationContext 接口中定义注册虚拟机钩子的方法 registerShutdownHook 和手动执行关闭的方法 close。
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {// ...@Overridepublic void registerShutdownHook() {Runtime.getRuntime().addShutdownHook(new Thread(this::close));}@Overridepublic void close() {getBeanFactory().destroySingletons();}
}
  1. 这里主要体现了关于注册钩子和关闭的方法实现,上文提到过的 Runtime.getRuntime().addShutdownHook,可以尝试验证。在一些中间件和监控系统的设计中也可以用得到,比如监测服务器宕机,执行备机启动操作

四、测试

  1. 事先准备
public class UserDao {private static Map<String, String> hashMap = new HashMap<>();public void initDataMethod(){System.out.println("执行:init-method");hashMap.put("10001", "小傅哥");hashMap.put("10002", "八杯水");hashMap.put("10003", "阿毛");}public void destroyDataMethod(){System.out.println("执行:destroy-method");hashMap.clear();}public String queryUserName(String uId) {return hashMap.get(uId);}}
public class UserService implements InitializingBean, DisposableBean {private String uId;private String company;private String location;private UserDao userDao;@Overridepublic void destroy() throws Exception {System.out.println("执行:UserService.destroy");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("执行:UserService.afterPropertiesSet");}// ...get/set
}
  1. UserDao,修改了之前使用 static 静态块初始化数据的方式,改为提供 initDataMethod 和 destroyDataMethod 两个更优雅的操作方式进行处理。 UserService,以实现接口 InitializingBean,
  2. DisposableBean 的两个方法destroy()、afterPropertiesSet(),处理相应的初始化和销毁方法的动作。afterPropertiesSet,方法名字很好,在属性设置后执行
  1. 配置文件
    基础配置,无BeanFactoryPostProcessor、BeanPostProcessor,实现类
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="userDao" class="cn.bugstack.springframework.test.bean.UserDao" init-method="initDataMethod" destroy-method="destroyDataMethod"/><bean id="userService" class="cn.bugstack.springframework.test.bean.UserService"><property name="uId" value="10001"/><property name="company" value="腾讯"/><property name="location" value="深圳"/><property name="userDao" ref="userDao"/></bean></beans>
  1. 配置文件中主要是新增了,init-method=“initDataMethod” destroy-method=“destroyDataMethod”,这样两个配置。从源码的学习中可以知道,这两个配置是为了加入到BeanDefinition 定义类之后写入到类 DefaultListableBeanFactory 中的beanDefinitionMap 属性中去。
  1. 单元测试
@Test
public void test_xml() {// 1.初始化 BeanFactoryClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");applicationContext.registerShutdownHook();      // 2. 获取Bean对象调用方法UserService userService = applicationContext.getBean("userService", UserService.class);String result = userService.queryUserInfo();System.out.println("测试结果:" + result);
}

测试方法中新增加了一个,注册钩子的动作。applicationContext.registerShutdownHook();

六、总结

  1. 本文主要完成了关于初始和销毁在使用接口定义 implements InitializingBean, DisposableBean和在spring.xml中配置 init-method="initDataMethod",destroy-method="destroyDataMethod" 的两种具体在AbstractAutowireCapableBeanFactory 完成初始方法和 AbstractApplicationContext 处理销毁动作的具体实现过程。
  2. 通过本文的实现内容,可以看到目前这个 Spring 框架对 Bean 的操作越来越完善了,可扩展性也不断的增强。你既可以在Bean注册完成实例化前进行 BeanFactoryPostProcessor 操作,也可以在Bean实例化过程中执行前置和后置操作,现在又可以执行Bean的初始化方法和销毁方法。所以一个简单的Bean对象,已经被赋予了各种扩展能力。
  3. 在学习和动手实践 Spring 框架学习的过程中,特别要注意的是它对接口和抽象类的把握和使用,尤其遇到类似,A继承B实现C时,C的接口方法由A继承的父类B实现,这样的操作都蛮有意思的。也是可以复用到通常的业务系统开发中进行处理一些复杂逻辑的功能分层,做到程序的可扩展、易维护等特性。

手写简版spring --7--初始化方法和销毁方法相关推荐

  1. 手写简版spring --9--对象作用域和FactoryBean

    一.目标 交给 Spring 管理的 Bean 对象,一定就是我们用类创建出来的 Bean 吗?创建出来的 Bean 就永远是单例的吗,没有可能是原型模式吗?在集合 Spring 框架下,我们使用的 ...

  2. 手写简版spring --8--Aware感知容器对象Aware感知容器对象

    一.目标 目前已实现的 Spring 框架,在 Bean 操作上能提供出的能力,包括:Bean 对象的定义和注册,以及在操作 Bean 对象过程中执行的,BeanFactoryPostProcesso ...

  3. 手写简版spring --6--应用上下文(BeanPostProcessor 和 BeanFactoryPostProcessor)

    一.目标 如果你在自己的实际工作中开发过基于 Spring 的技术组件,或者学习过关于SpringBoot 中间件设计和开发等内容.那么你一定会继承或者实现了 Spring对外暴露的类或接口,在接口的 ...

  4. 手写简版spring --10--容器事件和事件监听器

    一.降低耦合 解耦场景在互联网开发的设计中使用的也是非常频繁,如:这里需要一个注册完成事件推送消息.用户下单我会发送一个MQ.收到我的支付消息就可以发货了等等,都是依靠事件订阅和发布以及MQ消息这样的 ...

  5. 手写简版spring --2--实现Bean的定义、注册、获取

    一.目标 在上一章节我们初步依照 Spring Bean 容器的概念,实现了一个粗糙版本的代码实现.那么本章节我们需要结合已实现的 Spring Bean 容器进行功能完善,实现 Bean 容器关于 ...

  6. 手写简版spring --1--创建简单的Bean容器

    一.声明 这个系列是我自己的学习笔记,为了在学习的过程中巩固知识而记录的,好强迫自己用心拜读,而不是进收藏夹.本系列都是基于小缚哥的文章和代码的,想要深入了解,请移步小缚哥博客 二.spring-Be ...

  7. 手写简版spring --5--资源加载器解析文件注册对象

    一.目标 在完成 Spring 的框架雏形后,现在我们可以通过单元测试进行手动操作Bean对象的定义.注册和属性填充,以及最终获取对象调用方法.但这里会有一个问题,就是如果实际使用这个 Spring ...

  8. 手写简版spring --4--注入属性和依赖对象

    一.目标 首先我们回顾下这几章节都完成了什么,包括:实现一个容器.定义和注册Bean.实例化Bean,按照是否包含构造函数实现不同的实例化策略,那么在创建对象实例化这我们还缺少什么?其实还缺少一个关于 ...

  9. 手写简版spring --3--对象实例化策略

    一.目标 这一章节的目标主要是为了解决上一章节我们埋下的坑,那是什么坑呢?其实就是一个关于 Bean 对象在含有构造函数进行实例化的坑.在上一章节我们扩充了 Bean 容器的功能,把实例化对象交给容器 ...

最新文章

  1. 软件架构设计 温昱著 - 读书笔记
  2. 无法识别的属性 configProtectionProvider的解决方案
  3. 有关数据库的多库查询
  4. 从构建分布式秒杀系统聊聊WebSocket推送通知
  5. 【数据结构与算法】数组动态分配方式的思考
  6. RPC Demo(二) 基于 Zookeeper 的服务发现
  7. 罗永浩要造智能音箱;苹果承认bug;微软特制AI曝光 | 极客头条
  8. spark 部署安装
  9. 彼得林奇PEG价值选股策略(附源码入口)
  10. uniapp 微信小程序获取map地图中心的经纬度地理位置
  11. 多条Precision-Recall(PR)曲线绘制(PR曲线)含python代码
  12. https防止注入_离子注入新法,将钢的耐磨性提高百倍
  13. Vultr IP及SSH 故障排除信息
  14. 台式电脑怎么锁定该计算机,教大家电脑整个键盘锁了怎么办
  15. [转]中国大学计算机专业考研分析
  16. 15秒,找到行业内最专业的内容,你想要吗
  17. 视觉机器人+人体姿态识别项目总结
  18. [转]邓不利多所理解的预言
  19. 利用Excel 2010的“图表模板”功能,快速创建新图表
  20. 【SAAS】同城+速送+跑腿+约车+快狗+家政+车检

热门文章

  1. Golang的模板与渲染
  2. 简单记录js中的this关键字
  3. UNIX/Linux-进程控制(实例入门篇)
  4. mapReducer第一个例子WordCount
  5. 【荐】中国最有潜力的十位企业家(IT行业占大半)
  6. flash也玩p2p
  7. Linux源码编译(一):从头文件说起
  8. 反向传播(Back propagation)算法推导具体过程
  9. 网站运营之做到SEO操作视频教程【21讲】
  10. 宅福利-宅家抗疫,你我同在2020-01-30