Spring中Bean初始化与销毁实现

本文中源码来自Spring 5.3.x分支,github源码地址:github.com/spring-proj...

一 示例

Java 复制代码
package com.xiakexing.service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class UserService {

    public void test() {
        System.out.println("创建了UserService");
    }

    @PostConstruct
    public void afterConstruct() {
        System.out.println("UserService PostConstruct");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("UserService destroy");
    }
}
Java 复制代码
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.test();
context.close();

@PostConstruct和@PreDestroy注解的方法,是如何被调用的呢?

二 @PostConstruct实现

在创建Bean的初始化前阶段,将执行BeanPostProcessor.postProcessBeforeInitialization()。在InitDestroyAnnotationBeanPostProcessor实现类,完成了对@PostConstruct的查找和回调。

2.1 InitDestroyAnnotationBeanPostProcessor

来看类注释

Allows for an annotation alternative to Spring's org.springframework.beans.factory.InitializingBean and org.springframework.beans.factory.DisposableBean callback interfaces.

允许一个注释替代Spring的org.springframework.beans.factory.InitializingBean和org.springframework.beans.factory.DisposableBean回调接口。

Init and destroy annotations may be applied to methods of any visibility: public, package-protected, protected, or private. Multiple such methods may be annotated, but it is recommended to only annotate one single init method and destroy method, respectively.

Init和destroy注解可以应用于任何可见性的方法:public、package-protected、protected或private。可以注释多个这样的方法,但建议分别只注释一个init方法和destroy方法。

有以下结论:

  1. @PostConstruct是为代替InitializingBean接口,@PreDestroy是为代替DisposableBean接口;
  2. 这两个注解可修饰带有任何权限修饰符的方法;
  3. 在一个类中,可以多次使用这两个注解;但建议只注释一个init方法和destroy方法。

2.2 收集生命周期方法

首先在class中查找init和destory方法,封装为LifecycleElement对象,为每个Class创建一个LifecycleMetadata对象。 LifecycleMetadata一经构建,就放入lifecycleMetadataCache中缓存。

initAnnotationType其实就是PostConstruct.class,destroyAnnotationType就是PreDestroy.class。这两属性是在子类CommonAnnotationBeanPostProcessor中赋值的。

ReflectionUtils#doWithLocalMethods中,会遍历类中每个方法,执行MethodCallback.doWith。(函数式接口,类似于Consumer)

2.3 执行初始化方法

遍历initMethods,反射调用。

三 Bean的销毁

关闭Spring容器,通常有两种方式:

  1. 主动调用ApplicationContext.close();
  2. 或者ConfigurableApplicationContext.registerShutdownHook(),注册钩子,在进程结束时将关闭Spring容器。

在Spring容器将关闭前,想要对bean执行自定义的销毁动作。通常有以下实现方式:

  1. 实现DisposableBean接口,复写destroy();
  2. 实现AutoCloseable接口,复写close();
  3. BeanDefinition中指定了destroyMethod,如通过@Bean(destroyMethod="XXX");
  4. 使用@PreDestroy标注bean的销毁方法。而该注解就是为了代替DisposableBean接口。

在Bean被创建的最后一步,会将定义了销毁逻辑的Bean,注册到disposableBeans缓存中。

3.1 适配器模式

如果一个Bean实现了DisposableBean接口,或者实现了AutoCloseable接口,或者在BeanDefinition中指定了destroyMethod,那么这个Bean都属于"DisposableBean",在容器关闭时都要调用相应的销毁方法。

为了将各个销毁方式,统一适配成DisposableBean实现,就用到DisposableBeanAdapter。

3.2 Bean的销毁

关闭Spring容器时,会调用AbstractApplicationContext#doClose,有如下流程:

  1. 首先发布ContextClosedEvent事件;
  2. 调用lifecycleProcessor的onCloese()方法;
  3. 销毁单例Bean;
  4. 调用closeBeanFactory,关闭容器;
  5. 执行AbstractApplicationContext#onClose模板方法;

可见,此时会触发对单例Bean的销毁。在DefaultSingletonBeanRegistry#destroySingletons中,将遍历disposableBeans缓存:

  1. 把每个disposableBean从singletonObjects、singletonFactories中移除
  2. 如果这个disposableBean被其他Bean依赖了,那么先销毁依赖Bean
  3. 调用disposableBean的destroy(),销毁当前disposableBean;
  4. 如果当前disposableBean还包含了inner beans,将这些Bean也销毁;
  5. 清空各种缓存Map,包括singletonObjects、dependentBeanMap等。

3.3 @PreDestroy实现

DestructionAwareBeanPostProcessor接口,在Bean对象销毁之前,可执行自定义逻辑。

  • postProcessBeforeDestruction:Bean销毁前的自定义动作;
  • requiresDestruction:确定给定的bean实例是否需要执行这个后处理器,默认返回true。

当容器中有DestructionAwareBeanPostProcessor对象,且requiresDestruction方法对某个Bean返回true时,这个Bean也会被注册到disposableBeans缓存中。

当容器关闭时,对某个Bean执行DisposableBean.destroy()前,会先执行DestructionAwareBeanPostProcessor.postProcessBeforeDestruction方法。

源码中有个默认实现InitDestroyAnnotationBeanPostProcessor,完成了对@PreDestroy方法的回调。

相关推荐
Full Stack Developme31 分钟前
Java LRU 与 LFU 算法及应用
java·开发语言·算法
万联WANFLOW2 小时前
SD-WAN 控制平面高可用怎么做?SDWAN 控制器挂了,全网会发生什么
运维·网络·分布式·架构
踩着两条虫2 小时前
可视化 vs 终端 vs 云端:VTJ.PRO、Claude Code、Codex 三强横评
前端·vue.js·人工智能·低代码·架构
Fabarta3 小时前
从 0 实现 ChatGPT 风格的流式对话 UI
算法·架构
搭贝4 小时前
低代码平台私有化部署架构与信创全栈适配实战
低代码·架构·rxjava
shushangyun_4 小时前
2026智能采购商城系统选型指南:如何引领企业数字化采购升级
java·大数据·数据库·人工智能·机器学习
她说..4 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
学渣超4 小时前
记一次分布式事务数据不一致的排查之旅:从超时到索引,层层剥茧
java·后端·架构
小短腿的代码世界4 小时前
Qt Bluetooth源码深度解析:从HCI协议到跨平台API的完整架构
开发语言·qt·架构
掉鱼的猫4 小时前
Agent Harness 实战指南:构建生产级 AI Agent 的"马具"框架
java·llm·aigc