关于java:原来-springxml-配置的-destroymethod-需要用到向虚拟机注册钩子来实现

8次阅读

共计 13734 个字符,预计需要花费 35 分钟才能阅读完成。

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

积淀、分享、成长,让本人和别人都能有所播种!😄

一、前言

有什么形式,能给代码留条生路?

有人说:人人都是产品经理,那你晓得吗,人人也都能够是码农程序员!就像:

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

其实学会写代码并不难,但学会写好代码却很难。从易浏览上来说你的代码要有精确的命名和清晰的正文、从易使用上来说你的代码要具备设计模式的包装让对外的服务调用更简略、从易扩大上来说你的代码要做好业务和性能的实现分层。在易浏览、易使用、易扩大以及更多编码标准的束缚下,还须要在开发实现上线后的交付后果上满足;高可用、高性能、高并发,与此同时你还会接到现有我的项目中层出不穷来自产品经理新增的需要。

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

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

兄嘚,给代码留条生路吧!你的代码用上了定义接口吗、接口继承接口吗、接口由抽象类实现吗、类继承的类实现了接口办法吗,而这些操作都是为了让你的程序逻辑做到分层、分区、分块,把外围逻辑层和业务封装层做好隔离,当有业务变动时候,只须要做在业务层实现拆卸,而底层的外围逻辑服务并不需要频繁变动,它们所减少的接口也更原子化,不具备业务语意。所以这样的实现形式能力给你的代码留条生路。如果还不是太了解,能够多看看《重学 Java 设计模式》和当初编写的《手撸 Spring》,这外面都有大量的设计模式利用实际

二、指标

当咱们的类创立的 Bean 对象,交给 Spring 容器治理当前,这个类对象就能够被赋予更多的应用能力。就像咱们在上一章节曾经给类对象增加了批改注册 Bean 定义未实例化前的属性信息批改和实例化过程中的前置和后置解决,这些额定能力的实现,都能够让咱们对现有工程中的类对象做相应的扩大解决。

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

  • 须要满足用户能够在 xml 中配置初始化和销毁的办法,也能够通过实现类的形式解决,比方咱们在应用 Spring 时用到的 InitializingBean, DisposableBean 两个接口。
    - 其实还能够有一种是注解的形式解决初始化操作,不过目前还没有实现到注解的逻辑,后续再欠缺此类性能。

三、设计

可能面对像 Spring 这样宏大的框架,对外裸露的接口定义应用或者 xml 配置,实现的一系列扩展性操作,都让 Spring 框架看上去很神秘。其实对于这样在 Bean 容器初始化过程中额定增加的解决操作,无非就是事后执行了一个定义好的接口办法或者是反射调用类中 xml 中配置的办法,最终你只有依照接口定义实现,就会有 Spring 容器在解决的过程中进行调用而已。整体设计构造如下图:

  • 在 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

  • 以上整个类图构造形容进去的就是本次新增 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 实现,这样的操作都蛮有意思的。也是能够复用到通常的业务零碎开发中进行解决一些简单逻辑的性能分层,做到程序的可扩大、易保护等个性。

七、系列举荐

  • 【教训分享】码农云服务应用学习,部环境、开始口、配域名、弄 SSL、搭博客!
  • 方案设计:基于 IDEA 插件开发和字节码插桩技术,实现研发交付品质主动剖析
  • 工作两三年了,整不明确架构图都画啥?
  • 《SpringBoot 中间件设计和开发》| 对,小傅哥这次教你造火箭!
  • 工作 3 年,看啥材料能月薪 30K?
正文完
 0