DisposableBean源码分析

DisposableBean

一、基本信息

✒️ 作者 - Lex 📝 博客 - 我的CSDN 📚 文章目录 - 所有文章 🔗 源码地址 - DisposableBean源码

二、接口描述

DisposableBean 接口允许执行某些资源清理操作,比如我们可以使用这个接口来确保某些资源,比如文件句柄、网络连接、数据库连接等,被正确地释放或清理。

三、接口源码

DisposableBean 是 Spring 框架自 12.08.2003 开始引入的一个核心接口。如果bean在销毁时希望释放资源,那么可以实现此接口,另外实现 Java 的 AutoCloseable 接口以达到同样的目的

/**
 * 要实现此接口的 bean 在销毁时希望释放资源。
 * 当单独销毁作用域内的 bean 时,BeanFactory 会调用 destroy 方法。
 * 一个 org.springframework.context.ApplicationContext 应当在关闭时,
 * 根据应用的生命周期来销毁其所有的单例对象。
 *
 * Spring 管理的 bean 也可以实现 Java 的 AutoCloseable 接口以达到同样的目的。
 * 实现接口的另一种选择是指定一个自定义的销毁方法,例如在 XML bean 定义中。
 * 关于所有 bean 生命周期方法的列表,参见 BeanFactory BeanFactory。
 *
 * @author Juergen Hoeller
 * @since 12.08.2003
 * @see InitializingBean
 * @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName()
 * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#destroySingletons()
 * @see org.springframework.context.ConfigurableApplicationContext#close()
 */
public interface DisposableBean {

    /**
     * 当 bean 被其包含的 BeanFactory 销毁时调用。
     * @throws Exception 如果在关闭时出现错误。错误会被记录,
     *                   但不会被重新抛出,以允许其他 beans 正确释放他们的资源。
     */
    void destroy() throws Exception;

}

四、主要功能

  1. 销毁回调
    • 当 bean 被 Spring 容器销毁时,如果它实现了 DisposableBean 接口,容器会自动调用其 destroy() 方法。这为 beans 提供了一个机会在销毁之前执行任何必要的清理操作。

五、最佳实践

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个MyConfiguration组件类,最后调用context.close()方法关闭容器。

public class DisposableBeanApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        context.close();
    }
}

这里使用@Bean注解,定义了一个Bean,是为了确保MyDisposableBean被 Spring 容器执行。

@Configuration
public class MyConfiguration {

    @Bean
    public static MyDisposableBean myDisposableBean(){
        return new MyDisposableBean();
    }
}

MyDisposableBean类在实例化时会建立一个模拟的数据库连接,并且在销毁时会关闭这个连接。

public class MyDisposableBean implements DisposableBean {

    // 模拟的数据库连接对象
    private String databaseConnection;

    public MyDisposableBean() {
        // 在构造函数中模拟建立数据库连接
        this.databaseConnection = "Database connection established";
        System.out.println(databaseConnection);
    }

    @Override
    public void destroy() throws Exception {
        // 在 destroy 方法中模拟关闭数据库连接
        databaseConnection = null;
        System.out.println("Database connection closed");
    }
}

运行结果发现,当 MyDisposableBean bean 被销毁时,destroy() 方法确实被调用了,并模拟关闭了数据库连接。

Database connection established
Database connection closed

六、时序图

DisposableBeanApplication AnnotationConfigApplicationContext AbstractApplicationContext DefaultListableBeanFactory DefaultSingletonBeanRegistry DisposableBeanAdapter MyDisposableBean AnnotationConfigApplicationContext(componentClasses ) 应用开始初始化上下文 初始化完成 close() 请求关闭上下文 doClose() 执行关闭逻辑 destroyBeans() 开始销毁beans destroySingletons() 销毁单例beans super.destroySingletons() 调父类销毁方法 destroySingleton(beanName) 销毁单个bean super.destroySingleton(beanName) 调父类销毁bean方法 destroyBean(beanName,bean) 执行销毁bean操作 destroy() 适配器执行销毁 destroy() 执行自定义销毁逻辑 请求关闭上下文结束 DisposableBeanApplication AnnotationConfigApplicationContext AbstractApplicationContext DefaultListableBeanFactory DefaultSingletonBeanRegistry DisposableBeanAdapter MyDisposableBean DisposableBean时序图

七、源码分析

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个MyConfiguration组件类,然后通过调用 context.close() 方法来关闭应用上下文。

public class DisposableBeanApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        context.close();
    }
}

org.springframework.context.support.AbstractApplicationContext#close方法中,首先是启动了一个同步块,它同步在 startupShutdownMonitor 对象上。这确保了在给定时刻只有一个线程可以执行这个块内的代码,防止多线程导致的资源竞争或数据不一致,然后是调用了 doClose 方法,最后是为 JVM 注册了一个关闭钩子。

@Override
public void close() {
    synchronized (this.startupShutdownMonitor) {
        doClose();
        // If we registered a JVM shutdown hook, we don't need it anymore now:
        // We've already explicitly closed the context.
        if (this.shutdownHook != null) {
            try {
                Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
            }
            catch (IllegalStateException ex) {
                // ignore - VM is already shutting down
            }
        }
    }
}

org.springframework.context.support.AbstractApplicationContext#doClose方法中,又调用了 destroyBeans 方法。

protected void doClose() {
    // ... [代码部分省略以简化]
    // Destroy all cached singletons in the context's BeanFactory.
    destroyBeans();
    // ... [代码部分省略以简化]
}

org.springframework.context.support.AbstractApplicationContext#destroyBeans方法中,首先是调用了getBeanFactory()返回 Spring 的 BeanFactory ,然后在获得的 BeanFactory 上,调用了 destroySingletons 方法,这个方法的目的是销毁所有在 BeanFactory 中缓存的单例 beans。

protected void destroyBeans() {
    getBeanFactory().destroySingletons();
}

org.springframework.beans.factory.support.DefaultListableBeanFactory#destroySingletons方法中,首先是调用了父类的 destroySingletons 方法,为了确保继承自父类的销毁逻辑得到了执行。

@Override
public void destroySingletons() {
    super.destroySingletons();
    updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
    clearByTypeCache();
}

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroySingletons方法中,首先是在disposableBeans 字段上,从其键集合中获取所有的 bean 名称,并将它们转换为一个字符串数组。disposableBeans 可能包含了实现了 DisposableBean 接口的 beans,这些 beans 需要在容器销毁时特殊处理,最后倒序循环,从最后一个开始,销毁所有在 disposableBeans 列表中的 beans。这样做是为了确保依赖关系正确地处理,beans先被创建的应该后被销毁。

public void destroySingletons() {
    // ... [代码部分省略以简化]
    String[] disposableBeanNames;
    synchronized (this.disposableBeans) {
        disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
    }
    for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
        destroySingleton(disposableBeanNames[i]);
    }
    // ... [代码部分省略以简化]
}

org.springframework.beans.factory.support.DefaultListableBeanFactory#destroySingleton方法中,首先是调用了父类的 destroySingleton 方法,为了确保继承自父类的销毁逻辑得到了执行。

@Override
public void destroySingleton(String beanName) {
    super.destroySingleton(beanName);
    removeManualSingletonName(beanName);
    clearByTypeCache();
}

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroySingleton方法中,首先是使用 synchronized 关键字在 disposableBeans 对象上进行同步,以确保在多线程环境中安全地访问和修改它,从 disposableBeans 集合中移除指定名称的 bean,并将其转换为 DisposableBean 类型,最后调用destroyBean方法执行实际的销毁操作。

public void destroySingleton(String beanName) {
    // Remove a registered singleton of the given name, if any.
    removeSingleton(beanName);

    // Destroy the corresponding DisposableBean instance.
    DisposableBean disposableBean;
    synchronized (this.disposableBeans) {
        disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
    }
    destroyBean(beanName, disposableBean);
}

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroyBean方法中,直接调用 beandestroy 方法。因为 bean 是一个 DisposableBean 类型的实例,所以它一定有一个 destroy 方法,该方法提供了 bean 的自定义销毁逻辑。

protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
    // ... [代码部分省略以简化]
    // Actually destroy the bean now...
    if (bean != null) {
        try {
            bean.destroy();
        }
        catch (Throwable ex) {
            // ... [代码部分省略以简化]
        }
    }
    // ... [代码部分省略以简化]
}

org.springframework.beans.factory.support.DisposableBeanAdapter#destroy方法中,首先检查 invokeDisposableBean 变量,如果为true,就直接调用实现 DisposableBean 接口的 bean 的 destroy 方法。

@Override
public void destroy() {
    // ... [代码部分省略以简化]
    
    if (this.invokeDisposableBean) {
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
        }
        try {
            if (System.getSecurityManager() != null) {
                // ... [代码部分省略以简化]
            }
            else {
                ((DisposableBean) this.bean).destroy();
            }
        }
        catch (Throwable ex) {
            // ... [代码部分省略以简化]
        }
    }
}

最后执行到我们自定义的逻辑中, MyDisposableBean类在实例化时会建立一个模拟的数据库连接,并且在销毁时会关闭这个连接。

public class MyDisposableBean implements DisposableBean {

    // 模拟的数据库连接对象
    private String databaseConnection;

    public MyDisposableBean() {
        // 在构造函数中模拟建立数据库连接
        this.databaseConnection = "Database connection established";
        System.out.println(databaseConnection);
    }

    @Override
    public void destroy() throws Exception {
        // 在 destroy 方法中模拟关闭数据库连接
        databaseConnection = null;
        System.out.println("Database connection closed");
    }
}

八、注意事项

  1. 不要过度依赖

    • 尽管 DisposableBean 提供了一种定义销毁逻辑的标准方法,但更推荐使用 @PreDestroy 注解或在 bean 定义中指定 destroy-method 属性。这些方法通常更简单,更具有声明性,并且避免了不必要的代码耦合。
  2. 原型 bean

    • 对于原型作用域的 beans,Spring 不会管理它们的完整生命周期。这意味着对于原型 beans,DisposableBeandestroy() 方法不会被自动调用。应确保通过其他方式处理这些 bean 的资源释放。
  3. 与初始化方法配合

    • 如果我们正在使用 DisposableBean 来处理销毁逻辑,可能也会考虑使用 InitializingBean 来处理初始化逻辑。但同样,推荐使用 @PostConstruct 注解或 init-method 属性来定义初始化方法。
  4. 避免重复代码

    • 如果多个 beans 具有相似的销毁逻辑,考虑将这些逻辑提取到一个共享方法或帮助类中,以减少重复代码和增强可维护性。
  5. 避免长时间运行的操作

    • destroy() 方法应该快速执行并释放资源。避免在其中执行可能耗时的操作,因为这可能会延迟应用的关闭过程。

九、总结

最佳实践总结
  1. 应用启动

    • 我们创建了一个启动类 DisposableBeanApplication,其中初始化了一个基于注解的应用上下文 AnnotationConfigApplicationContext。这个上下文根据 MyConfiguration 类配置了 Spring 容器。
  2. Java配置

    • MyConfiguration 类中,我们使用 @Bean 注解定义了一个名为 myDisposableBean 的 bean。这确保了 MyDisposableBean 类的实例被 Spring 容器管理。
  3. 资源管理

    • MyDisposableBean 类模拟了数据库的连接和断开过程。当这个类被实例化时,它模拟建立了一个数据库连接。然后,当这个 bean 被销毁时(由于上下文的关闭),它的 destroy() 方法被调用,模拟了数据库连接的关闭。
  4. 应用关闭

    • 在启动类的 main 方法的最后,通过调用 context.close() 方法来关闭应用上下文。这导致容器销毁所有单例 beans。在这个过程中,MyDisposableBean bean 的 destroy() 方法被调用,从而模拟关闭了数据库连接。
  5. 输出结果

    • 运行程序时,我们首先看到 "Database connection established",这是 MyDisposableBean 构造函数中的输出,表示模拟的数据库连接已经建立。随后,当应用上下文关闭时,我们看到 "Database connection closed",这是 MyDisposableBeandestroy() 方法中的输出,表示模拟的数据库连接已经关闭。
源码分析总结
  1. 启动和关闭:

    • 我们创建了 DisposableBeanApplication 启动类,其中初始化了一个基于注解的应用上下文 AnnotationConfigApplicationContext

    • 通过传递 MyConfiguration 类作为构造参数来配置 Spring 容器。

    • 使用 context.close() 方法来关闭应用上下文,触发资源的清理和释放。

  2. 关闭的同步操作:

    • AbstractApplicationContext#close 方法中,启动了一个同步块,确保在给定时刻只有一个线程可以关闭应用上下文,防止资源竞争或数据不一致。

    • 关闭上下文后,任何先前注册的 JVM 关闭钩子都会被移除,因为上下文已经明确地被关闭了。

  3. 实际关闭操作:

    • AbstractApplicationContext#doClose 方法中,调用 destroyBeans 方法来销毁容器中的 beans。
  4. 销毁beans:

    • 通过 AbstractApplicationContext#destroyBeans 方法,BeanFactory 调用其 destroySingletons 方法来销毁所有缓存的单例 beans。

    • DefaultListableBeanFactory 中,首先确保调用了其父类的销毁逻辑。

    • DefaultSingletonBeanRegistry#destroySingletons 会获取所有需要销毁的 beans 的名称,并以创建的相反顺序进行销毁,以正确处理依赖关系。

  5. 具体的bean销毁:

    • 对于每个要销毁的 bean,都会调用 DefaultSingletonBeanRegistry#destroySingleton 方法。

    • 如果 bean 实现了 DisposableBean 接口,它的 destroy 方法会被调用。

    • 为了确保线程安全,许多操作都在同步块中执行,特别是当操作 disposableBeans 时。

  6. 自定义销毁逻辑:

    • 最终,到达我们定义的 MyDisposableBean 类。当这个类的实例被销毁时,它会模拟关闭一个数据库连接。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SpringBean生命周期是由Spring容器负责管理的,主要包括以下阶段: 1. 实例化:Spring容器根据配置信息或注解,创建Bean的实例。这一过程一般通过Java反射机制实现。 2. 属性注入:Spring容器将依赖注入到Bean的属性中,可以通过构造函数、Setter方法或者字段注入来实现。 3. 初始化:在Bean实例创建完成后,Spring容器会调用一系列的初始化回调方法。常见的初始化回调方法有`@PostConstruct`注解、`InitializingBean`接口的`afterPropertiesSet()`方法以及自定义的初始化方法。 4. 使用:Bean实例被使用,执行业务逻辑。 5. 销毁:当Bean不再被使用时,Spring容器会调用一系列的销毁回调方法。常见的销毁回调方法有`@PreDestroy`注解、`DisposableBean`接口的`destroy()`方法以及自定义的销毁方法。 下面是一个简化的示例代码,展示了Bean生命周期的常用方法: ```java public class MyBean implements InitializingBean, DisposableBean { private String name; public MyBean() { System.out.println("Bean实例化"); } public void setName(String name) { this.name = name; } @Override public void afterPropertiesSet() throws Exception { System.out.println("初始化回调方法"); } public void doSomething() { System.out.println("执行业务逻辑:" + name); } @Override public void destroy() throws Exception { System.out.println("销毁回调方法"); } } ``` 此外,Spring还提供了更细粒度的扩展点,如BeanPostProcessor接口和BeanFactoryPostProcessor接口,可以在Bean的实例化和初始化过程中进行自定义处理。 以上是一个简单的概述,实际的源码分析涉及到Spring框架的很多细节,包括BeanDefinition、BeanFactory、ApplicationContext等。你可以参考Spring源码来深入了解Bean生命周期的实现细节。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值