跳至主要內容

第07章:初始化和销毁方法

AruNi_Lu框架Spring约 2060 字大约 7 分钟

本文内容

1. 设计

当我们把某个类创建的 Bean 对象交给 Spring 容器管理后,该类对象就可以被赋予更多的使用能力。例如在上一章中,我们就给类对象添加了 对 BeanDefinition 未实例化前的属性信息的修改能力,以及 初始化过程中的前置处理和后置处理,让 Bean 对象实例化前后可以进行修改或替换。

这些额外能力的实现,都可以让我们对现有工程中的类对象做相应的 扩展处理

所以,我们还希望可以在 Bean 初始化过程中,执行一些额外操作,例如做数据的加载执行、链接注册中心暴露 RPC 接口、在 Web 程序关闭时执行链接断开、内存销毁等。

实现方式也有多种,一种是可以在 spring.xml 中配置,就像下面这样,此方式是 通过在 XML 中获取对应标签中的方法名,然后获取该方法,再通过反射执行该方法 即可:

image-20230323195834654

另一种实现方式是通过 实现接口,如果 有 Bean 实现了对应的接口,则会调用对应的初始化/销毁方法

还有一种则是通过 注解 实现,这个放到后续完善。

所以下面就来看看前两种方式应该如何实现。

2. 实现

总体的实现思路如下:

img

2.1 通过接口实现

如果通过接口实现的方式进行调用初始化/销毁方法,那么首先要做的,当然是提供接口,让某个 Bean 需要使用初始化/销毁方法时,只需实现我们提供的接口,实现对应的方法即可。

所以新增两个接口 InitializingBean 和 DisposableBean,在其中分别定义初始化方法 afterPropertiesSet()(进行属性填充后再调用),销毁方法 destroy()

2.2 通过 XML 配置实现

如果从 XML 文件中获取调用的方法名,再得到对应的方法,使用反射执行该方法(method.invoke()),则需要先在 BeanDefinition 中添加对应的属性(initMethodName、destroyMethodName),在进行 XML 解析时将得到的方法名存入 BeanDefinition,后续就可以从 BeanDefinition 中获取到对应的方法。

从而也得在 XmlBeanDefinitionReader 类中解析 XML 的时候,将 XML 配置中的 init-method、destroy-method 读取出来,然后设置到 beanDefinition 中,再进行注册 Bean。

2.3 初始化方法的执行

在上一章中,AbstractAutowireCapableBeanFactory 类中的 initializeBean() 方法中执行 Bean 对象的初始化方法 invokeInitMethods(),只是还未实现。

所以现在来实现此方法即可,由于现在我们提供了两种方式执行该方法,所以逻辑如下:

  • 方式一、实现了接口 InitializingBean:此时直接将当前 Bean 强转成 InitializingBean 类型后,调用 afterPropertiesSet() 即可执行实现类中的初始化方法;
  • 方式二、XML 文件中的 init-method:此时通过 BeanDefinition 获取 initMethodName 属性,然后通过 Class 获取到该方法,通过反射执行即可(initMethod.invoke(bean))。

2.4 销毁方法的执行

销毁方法适配器

首先,需要先新定义一个适配器类 DisposableBeanAdapter,实现 DisposableBean,实现 destroy() 方法,在此方法中定义我们目前提供的两种方式来执行 destroy() 方法。提供一个适配器类是因为我们提供的销毁方法有两种甚至多种方式,所以在执行销毁时不用去关注销毁哪些类型的方法,由这个适配器统一执行。

存储含有销毁方法的 Bean

那么继续来思考,我们怎么来判断一个 Bean 是否有销毁方法呢?因为销毁方法毕竟不是在注册时进行的,所以无法在注册时就判断。那想要知道哪些 Bean 需要执行销毁方法,最简单的方法就是 用一个容器将有销毁方法的 Bean 存储起来,当虚拟机关闭或主动销毁时,就可以先将这个容器中的 Bean 取出来,执行它们的销毁方法。

在接口 ConfigurableBeanFactory 中新定义 destroySingletons() 销毁方法,并由 AbstractBeanFactory(AbstractBeanFactory 实现了 ConfigurableBeanFactory)的父类 DefaultSingletonBeanRegistry 实现该方法。这里把实现接口的方法交给父类处理,是一种不错的隔离分层服务的设计技巧,把 Bean 的注册和销毁都交给了 DefaultSingletonBeanRegistry 管理,而不是放到 BeanFactory 中。

所以这里我们也把这个容器定义在 DefaultSingletonBeanRegistry 类中,然后提供一个注册方法 registerDisposableBean(String beanName, DisposableBean bean)。上面的 destroySingletons() 方法的实现,具体的步骤就是从容器中获取所有 DisposableBean,然后从容器中移除,再执行 destroy() 方法。

注意这个容器的 value 为 DisposableBean,上面的适配器类 DisposableBeanAdapter 实现了 DisposableBean,所以在注册时就可以使用 registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition)); ,将这个适配器当成 value 注册进这个容器中,到时候从容器中获取到的就是适配器对象,调用 destroy() 走的就是适配器中的该方法,然后再由适配器具体去判断使用什么方式执行销毁方法

调用执行销毁方法

另外,销毁方法的执行比较隐晦,因为一种情况是销毁方法需要在虚拟机执行关闭之前进行操作,所以需要一个 钩子 来执行具体的销毁方法。

为了方便用户使用,我们将注册钩子方法 registerShutdownHook() 定义在上下文的 ConfigurableApplicationContext 接口中,同时再定义一个 close() 方法。当钩子函数触发时,就执行 close() 调用上面的 destroySingletons()

这两个方法的实现类自然而然就在 AbstractApplicationContext 类中实现了。注册钩子函数的代码如下:

@Override
public void registerShutdownHook() {
    Runtime.getRuntime().addShutdownHook(new Thread(this::close));
}
  • 表示在虚拟机要关闭时,就会触发此钩子函数,调用 close() 方法。

close() 方法如下:

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

2.5 目录结构

到此,所有的设计就完成了,来看看目录结构的更变(绿色—新增、蓝色—修改):

image-20230324214722933

2.6 类结构图

3. 测试

UserDao 新增初始化方法和销毁方法,使用 XML 方式进行配置:

public class UserDao {

    private static Map<String, String> map = new HashMap<>();


    public void initDataMethod() {
        System.out.println("userDao 执行 init-method");
        map.put("10001", "孙悟空");
        map.put("10002", "猪八戒");
        map.put("10003", "沙悟净");
    }

    public void destroyDataMethod() {
        System.out.println("userDao 执行 destroy-method");
        map.clear();
    }

    public String queryUserName(String uId) {
        return map.get(uId);
    }
}

spring.xml 文件增加 userDao Bean 的初始化和销毁标签:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <!-- userDao 通过 xml 文件来执行初始化方法和销毁方法 -->
    <bean id="userDao" class="com.run.test.bean.UserDao" init-method="initDataMethod" destroy-method="destroyDataMethod"/>

    <bean id="userService" class="com.run.test.bean.UserService">
        <property name="uId" value="10001"/>
        <property name="company" value="腾讯"/>
        <property name="location" value="深圳"/>
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>

UserService 实现 InitializingBean 和 DisposableBean 接口,通过实现接口的方式执行初始化和销毁方法:

public class UserService implements InitializingBean, DisposableBean {

    private String uId;
    private String company;
    private String location;

    // 依赖 UserDao
    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");
    }

    public String queryUserInfo() {
        return userDao.queryUserName(uId) + ",公司:" + company + ",地点:" + location;
    }
    
    // setter、getter
}

Test 方法:

@Test
public void test_InitAndDestroy() {
    // 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);
}

输出结果:

userDao 执行 init-method
执行 UserService.afterPropertiesSet
测试结果:孙悟空,公司:腾讯,地点:深圳
执行 UserService.destroy
userDao 执行 destroy-method

Process finished with exit code 0

可以看到,两种方式都生效了。

4. 流程

该流程包含了上一章的扩展功能:

上次编辑于: