6

原来 spring.xml 配置的 destroy-method 需要用到向虚拟机注册钩子来实现!

 3 years ago
source link: https://my.oschina.net/itstack/blog/5088215
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
原来 spring.xml 配置的 destroy-method 需要用到向虚拟机注册钩子来实现!

持续坚持原创输出,点击蓝字关注我吧



作者:小傅哥博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!😜

    • 1. 工程结构

    • 2. 定义初始化和销毁方法的接口

    • 3. Bean属性定义新增初始化和销毁

    • 4. 执行 Bean 对象的初始化方法

    • 5. 定义销毁方法适配器(接口和配置)

    • 6. 创建Bean时注册销毁方法对象

    • 7. 虚拟机关闭钩子注册调用销毁方法

    • 1. 事先准备

    • 2. 配置文件

    • 3. 单元测试

  • 七、系列推荐

有什么方式,能给代码留条活路?

f3fd5127-bb03-4077-9893-5a573179ba5f.png

有人说:人人都是产品经理,那你知道吗,人人也都可以是码农程序员!就像:

  • 编程就是;定义属性、创建方法、调用展示
  • Java 和 PHP 就像男人和女人,前者在乎架构化模块后,后者在乎那个颜色我喜欢
  • 用心写,但不要不做格式化
  • 初次和产品对接的三个宝:砖头、铁锹、菜刀,分别保证有用、可用、好用
  • 从一行代码到一吨代码,开发越来越难,壁垒也越来越高

其实学会写代码并不难,但学会写好代码却很难。从易阅读上来说你的代码要有准确的命名和清晰的注释、从易使用上来说你的代码要具备设计模式的包装让对外的服务调用更简单、从易扩展上来说你的代码要做好业务和功能的实现分层。在易阅读、易使用、易扩展以及更多编码规范的约束下,还需要在开发完成上线后的交付结果上满足;高可用、高性能、高并发,与此同时你还会接到现有项目中层出不穷来自产品经理新增的需求。

🎙怎么办?知道你在码砖,不知道你在盖哪个猪圈!

就算码的砖是盖的猪圈,也得因为猪多扩面积、改水槽、加饲料呀,所以根本没法保证你写完的代码就不会加需求。那么新加的需求如果是以破坏了你原有的封装了非常完美500行的 ifelse 咋办,拆了重盖吗?

兄嘚,给代码留条活路吧!你的代码用上了定义接口吗、接口继承接口吗、接口由抽象类实现吗、类继承的类实现了接口方法吗,而这些操作都是为了让你的程序逻辑做到分层、分区、分块,把核心逻辑层和业务封装层做好隔离,当有业务变化时候,只需要做在业务层完成装配,而底层的核心逻辑服务并不需要频繁变化,它们所增加的接口也更原子化,不具备业务语意。所以这样的实现方式才能给你的代码留条活路。如果还不是太理解,可以多看看《重学Java设计模式》和现在编写的《手撸Spring》,这里面都有大量的设计模式应用实践

当我们的类创建的 Bean 对象,交给 Spring 容器管理以后,这个类对象就可以被赋予更多的使用能力。就像我们在上一章节已经给类对象添加了修改注册Bean定义未实例化前的属性信息修改和实例化过程中的前置和后置处理,这些额外能力的实现,都可以让我们对现有工程中的类对象做相应的扩展处理。

那么除此之外我们还希望可以在 Bean 初始化过程,执行一些操作。比如帮我们做一些数据的加载执行,链接注册中心暴漏RPC接口以及在Web程序关闭时执行链接断开,内存销毁等操作。如果说没有Spring我们也可以通过构造函数、静态方法以及手动调用的方式实现,但这样的处理方式终究不如把诸如此类的操作都交给 Spring 容器来管理更加合适。 因此你会看到到 spring.xml 中有如下操作:

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

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

60919b8b-9fe8-48a6-9327-19005779667b.png
  • 在 spring.xml 配置中添加 init-method、destroy-method 两个注解,在配置文件加载的过程中,把注解配置一并定义到 BeanDefinition 的属性当中。这样在 initializeBean 初始化操作的工程中,就可以通过反射的方式来调用配置在 Bean 定义属性当中的方法信息了。另外如果是接口实现的方式,那么直接可以通过 Bean 对象调用对应接口定义的方法即可, ((InitializingBean) bean).afterPropertiesSet(),两种方式达到的效果是一样的。
  • 除了在初始化做的操作外, destroy-methodDisposableBean 接口的定义,都会在 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
    │           │   │   ├── factory
    │           │   │   │   ├── 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

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring 应用上下文和对Bean对象扩展机制的类关系,如图 8-4

53767bae-efb0-44d2-b377-5146acf4fc39.png 图 8-4
  • 以上整个类图结构描述出来的就是本次新增 Bean 实例化过程中的初始化方法和销毁方法。
  • 因为我们一共实现了两种方式的初始化和销毁方法,xml配置和定义接口,所以这里既有 InitializingBean、DisposableBean 也有需要 XmlBeanDefinitionReader 加载 spring.xml 配置信息到 BeanDefinition 中。
  • 另外接口 ConfigurableBeanFactory 定义了 destroySingletons 销毁方法,并由 AbstractBeanFactory 继承的父类 DefaultSingletonBeanRegistry 实现 ConfigurableBeanFactory 接口定义的 destroySingletons 方法。 这种方式的设计可能数程序员是没有用过的,都是用的谁实现接口谁完成实现类,而不是把实现接口的操作又交给继承的父类处理。所以这块还是蛮有意思的,是一种不错的隔离分层服务的设计方式
  • 最后就是关于向虚拟机注册钩子,保证在虚拟机关闭之前,执行销毁操作。 Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!")));

2. 定义初始化和销毁方法的接口

cn.bugstack.springframework.beans.factory.InitializingBean

public interface InitializingBean {

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

}

cn.bugstack.springframework.beans.factory.DisposableBean

public interface DisposableBean {

void destroy() throws Exception;

}
  • InitializingBean、DisposableBean,两个接口方法还是比较常用的,在一些需要结合 Spring 实现的组件中,经常会使用这两个方法来做一些参数的初始化和销毁操作。比如接口暴漏、数据库数据读取、配置文件加载等等。

3. Bean属性定义新增初始化和销毁

cn.bugstack.springframework.beans.factory.config.BeanDefinition

public class BeanDefinition {

private Class beanClass;

private PropertyValues propertyValues;

private String initMethodName;

private String destroyMethodName;

// ...get/set
}
  • 在 BeanDefinition 新增加了两个属性:initMethodName、destroyMethodName,这两个属性是为了在 spring.xml 配置的 Bean 对象中,可以配置 init-method="initDataMethod" destroy-method="destroyDataMethod" 操作,最终实现接口的效果是一样的。 只不过一个是接口方法的直接调用,另外是一个在配置文件中读取到方法反射调用

4. 执行 Bean 对象的初始化方法

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

@Override
    protected 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. 实现接口 InitializingBean
        if (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);
        }
    }

}
  • 抽象类 AbstractAutowireCapableBeanFactory 中的 createBean 是用来创建 Bean 对象的方法,在这个方法中我们之前已经扩展了 BeanFactoryPostProcessor、BeanPostProcessor 操作,这里我们继续完善执行 Bean 对象的初始化方法的处理动作。
  • 在方法 invokeInitMethods 中,主要分为两块来执行实现了 InitializingBean 接口的操作,处理 afterPropertiesSet 方法。另外一个是判断配置信息 init-method 是否存在,执行反射调用 initMethod.invoke(bean)。这两种方式都可以在 Bean 对象初始化过程中进行处理加载 Bean 对象中的初始化操作,让使用者可以额外新增加自己想要的动作。

5. 定义销毁方法适配器(接口和配置)

cn.bugstack.springframework.beans.factory.support.DisposableBeanAdapter

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();
    }

@Override
    public void destroy() throws Exception {
        // 1. 实现接口 DisposableBean
        if (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);
        }

}

}
  • 可能你会想这里怎么有一个适配器的类呢,因为销毁方法有两种甚至多种方式,目前有 实现接口 DisposableBean配置信息 destroy-method,两种方式。而这两种方式的销毁动作是由 AbstractApplicationContext 在注册虚拟机钩子后看,虚拟机关闭前执行的操作动作。
  • 那么在销毁执行时不太希望还得关注都销毁那些类型的方法,它的使用上更希望是有一个统一的接口进行销毁,所以这里就新增了适配类,做统一处理。

6. 创建Bean时注册销毁方法对象

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

@Override
    protected 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));
        }
    }

}
  • 在创建 Bean 对象的实例的时候,需要把销毁方法保存起来,方便后续执行销毁动作进行调用。
  • 那么这个销毁方法的具体方法信息,会被注册到 DefaultSingletonBeanRegistry 中新增加的 Map<String, DisposableBean> disposableBeans 属性中去,因为这个接口的方法最终可以被类 AbstractApplicationContext 的 close 方法通过 getBeanFactory().destroySingletons() 调用。
  • 在注册销毁方法的时候,会根据是接口类型和配置类型统一交给 DisposableBeanAdapter 销毁适配器类来做统一处理。 实现了某个接口的类可以被 instanceof 判断或者强转后调用接口方法

7. 虚拟机关闭钩子注册调用销毁方法

cn.bugstack.springframework.context.ConfigurableApplicationContext

public interface ConfigurableApplicationContext extends ApplicationContext {

void refresh() throws BeansException;

void registerShutdownHook();

void close();

}
  • 首先我们需要在 ConfigurableApplicationContext 接口中定义注册虚拟机钩子的方法 registerShutdownHook 和手动执行关闭的方法 close

cn.bugstack.springframework.context.support.AbstractApplicationContext

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

// ...

@Override
    public void registerShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(this::close));
    }

@Override
    public void close() {
        getBeanFactory().destroySingletons();
    }

}
  • 这里主要体现了关于注册钩子和关闭的方法实现,上文提到过的 Runtime.getRuntime().addShutdownHook,可以尝试验证。在一些中间件和监控系统的设计中也可以用得到,比如监测服务器宕机,执行备机启动操作。

1. 事先准备

cn.bugstack.springframework.test.bean.UserDao

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);
    }

}

cn.bugstack.springframework.test.bean.UserService

public class UserService implements InitializingBean, DisposableBean {

private String uId;
    private String company;
    private String location;
    private UserDao userDao;

@Override
    public void destroy() throws Exception {
        System.out.println("执行:UserService.destroy");
    }

@Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行:UserService.afterPropertiesSet");
    }

// ...get/set
}
  • UserDao,修改了之前使用 static 静态块初始化数据的方式,改为提供 initDataMethod 和 destroyDataMethod 两个更优雅的操作方式进行处理。
  • UserService,以实现接口 InitializingBean, DisposableBean 的两个方法 destroy()、afterPropertiesSet(),处理相应的初始化和销毁方法的动作。 afterPropertiesSet,方法名字很好,在属性设置后执行

2. 配置文件

基础配置,无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>
  • 配置文件中主要是新增了, init-method="initDataMethod" destroy-method="destroyDataMethod",这样两个配置。从源码的学习中可以知道,这两个配置是为了加入到 BeanDefinition 定义类之后写入到类 DefaultListableBeanFactory 中的 beanDefinitionMap 属性中去。

3. 单元测试

@Test
public void test_xml() {
    // 1.初始化 BeanFactory
    ClassPathXmlApplicationContext 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();

测试结果

执行:init-method
执行:UserService.afterPropertiesSet
测试结果:小傅哥,腾讯,深圳
执行:UserService.destroy
执行:destroy-method

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

七、系列推荐


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK