Spring Bean 生命周期详解——简单、清晰、全面、实用

前言

在日常使用 Spring 框架进行开发时,我们享受着依赖注入(IoC)带来的便利,但你是否曾好奇一个 Bean 是如何从字节码变成容器中一个 ready-to-use 的组件?理解 Spring Bean 的生命周期,它能让你真正驾驭 Spring,在复杂业务场景中实现更优雅的功能扩展。本文将带你从核心流程出发,逐步深入各个扩展点,并通过代码示例讲解其实际应用场景。

一、Spring Bean 生命周期的核心骨架

抛开所有扩展点,一个 Bean 最基础的生命周期非常简单,仅包含四个步骤:

  1. 实例化(Instantiation):通过构造函数或工厂方法创建 Bean 的实例。
  2. 属性填充(Population) :Spring 通过反射,完成依赖注入(为 @Autowired@Value 等注解标记的字段赋值)。
  3. 初始化(Initialization):如果指定了初始化方法,则调用它。
  4. 销毁(Destruction):在容器关闭时,如果指定了销毁方法,则调用它。

这是一个朴素的骨架,Spring 强大的扩展能力都基于此构建。

如下为 Spring Bean 最基础的生命周期示例代码:

  1. 定义一个bean:LifeCircleBean
java 复制代码
public class LifeCircleBean {
    private DependencyBean dependencyBean;

    // 1. 实例化:构造函数
    public LifeCircleBean() {
        System.out.println("【1. 实例化】LifeCircleBean - 调用构造函数");
    }

    // 2. 属性填充:通过setter方法进行依赖注入
    @Autowired
    public void setDependencyBean(DependencyBean dependencyBean) {
        System.out.println("【2. 属性填充】LifeCircleBean - 依赖注入 (setDependencyBean)");
        this.dependencyBean = dependencyBean;
    }

    // 3. 初始化方法
    public void init() {
        System.out.println("【3. 初始化】LifeCircleBean - 自定义init-method被调用");
        System.out.println("在init()中,dependencyBean 是否为空? " + (dependencyBean == null));
    }

    // 4. 销毁方法
    public void destroy() {
        System.out.println("【4. 销毁】LifeCircleBean - 自定义destroy-method被调用");
    }

    public void doSomething() {
        System.out.println("LifeCircleBean is doing something");
    }
}

class DependencyBean {

}
  1. 配置类
java 复制代码
@Configuration
public class LifeCircleConfig {
    @Bean
    public DependencyBean dependencyBean() {
        return new DependencyBean();
    }

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public LifeCircleBean lifeCircleBean() {
        return new LifeCircleBean();
    }
}
  1. 测试代码:
java 复制代码
public class LifeCircleTest {
    public static void main(String[] args) {
        // 开始创建Spring应用上下文
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(LifeCircleConfig.class);

        // LifeCircleBean 并使用
        LifeCircleBean service = context.getBean(LifeCircleBean.class);
        service.doSomething();

        // 关闭容器
        context.close();
    }
}

输出结果:

console 复制代码
【1. 实例化】LifeCircleBean - 调用构造函数
【2. 属性填充】LifeCircleBean - 依赖注入 (setDependencyBean)
【3. 初始化】LifeCircleBean - 自定义init-method被调用
在init()中,dependencyBean 是否为空? false
LifeCircleBean is doing something
【4. 销毁】LifeCircleBean - 自定义destroy-method被调用

二、Spring 官方接口扩展点

Spring 提供了两个接口,允许我们在 Bean 的生命周期中插入自定义逻辑。

1. InitializingBean

InitializingBean 接口提供了一个 afterPropertiesSet() 方法,它在属性填充完成后,在自定义初始化方法之前被调用

应用场景:用于执行那些在 Bean 的依赖注入完成后,业务逻辑开始前,进行一些必须的复杂初始化工作。

  • 初始化消息队列(如Kafka、RocketMQ)的生产者/消费者、缓存预热、支付网关客户端、OSS对象存储客户端等,并验证配置是否正确。
  • 为何适用:保证外部依赖可用性,实现快速失败(Fail-Fast),避免运行时因配置错误或服务不可用导致故障。

基于接口 InitializingBean 实现配置验证和oss客户端初始化示例代码:

java 复制代码
@Component
public class OssClient implements InitializingBean {
    @Value("${oss.endpoint}")
    private String endpoint;
    @Value("${oss.accessKeyId}")
    private String accessKeyId;
    @Value("${oss.accessKeySecret}")
    private String accessKeySecret;
    private OSSClient ossClient;

    // 实现 InitializingBean 接口,在属性填充完成后执行验证逻辑
    @Override
    public void afterPropertiesSet() throws Exception {
        // 验证配置
        if (StringUtils.isAnyBlank(endpoint, accessKeyId, accessKeySecret)) {
            throw new IllegalStateException("OSS配置不完整");
        }
        // 初始化OSS客户端
        ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
    }
}

2. DisposableBean

DisposableBean 接口提供了一个 destroy() 方法,它在 Bean 被销毁时最先被调用

应用场景:在 Bean 被容器销毁前,进行一些"清理工作",确保资源被正确释放,避免内存泄漏和数据不一致。

  • 向自定义监控系统注册当前服务实例、启动后台任务线程(如定时刷新配置),并在应用关闭时安全地关闭这些线程、释放连接资源、反注册实例。

  • 为何适用:确保资源有序释放,避免内存泄漏或数据不一致。例如,在容器销毁Bean之前(@PreDestroy 或 destroy())关闭资源。

基于DisposableBean实现Nacos服务反注册示例代码:

java 复制代码
// 这是一个模拟 NacosServiceRegistry 的简化类,展示原理
public class NacosServiceRegistry implements ServiceRegistry<Registration>, DisposableBean {

    private final NamingService namingService; // Nacos 客户端
    private Registration registration; // 当前服务的注册信息
    private String instanceId; // 当前服务实例的ID

    // 构造函数,接收依赖
    public NacosServiceRegistry(NamingService namingService) {
        this.namingService = namingService;
    }

    // 实现 DisposableBean 接口,在Bean销毁时自动回调
    @Override
    public void destroy() throws Exception {
        // 执行反注册逻辑
        deregister();
    }

    // 反注册方法
    @Override
    public void deregister(Registration registration) {
        try {
            // 调用 Nacos 客户端进行反注册
            namingService.deregisterInstance(registration.getServiceId(), registration.getHost(), registration.getPort());
            System.out.println("Service deregistered from Nacos: " + registration.getServiceId());
        } catch (NacosException e) {
            throw new RuntimeException("Failed to deregister service", e);
        }
    }

    // ... 其他方法
}

注意 :现在的NacosServiceRegistry类实现注册和反注册并非通过实现以上2个接口实现的,而是通过以下两种方式协同工作:

  1. ApplicationListener :监听 Spring 容器的关闭事件
    • 注册时机:监听 WebServerInitializedEvent 事件
    • 反注册时机:由独立的Bean监听 ContextClosedEvent 事件
  2. AbstractAutoServiceRegistration:一个抽象父类,它提供了基于事件的注册/反注册模板方法。

三、Java 标准注解扩展点 (JSR-250)

为了解耦,我们通常使用 Java 公共注解(JSR-250)来定义初始化和销毁方法。

1. @PostConstruct

@PostConstruct 注解标记的方法,在 Bean 完成依赖注入后立即执行,效果等同于 InitializingBean.afterPropertiesSet(),但无侵入性。

资源初始化和缓存预热示例代码:

java 复制代码
@Service
public class CacheService {
    @Autowired
    private ProductRepository productRepository;
    private Map<Long, Product> cache;

    @PostConstruct
    public void preloadCache() {
        // 将商品信息加载到本地缓存
        List<Product> products = productRepository.findAll();
        cache = products.stream()
                       .collect(Collectors.toMap(Product::getId, Function.identity()));
    }
}

2. @PreDestroy

@PreDestroy 注解标记的方法,在 Bean 被容器销毁之前执行,效果等同于 DisposableBean.destroy(),但无侵入性。

安全关闭线程池示例代码:

java 复制代码
@Service
public class BackgroundTaskService {
    private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

    @PostConstruct
    public void startTask() {
        scheduler.scheduleAtFixedRate(this::refresh, 1, 1, TimeUnit.HOURS);
    }

    @PreDestroy
    public void shutdown() {
        // 优雅关闭,等待已有任务完成
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(10, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
        }
    }
}

加入扩展的初始化和销毁后的生命周期

如下为 Spring Bean 加入扩展点的生命周期示例代码:

  1. 定义一个bean:ExtLifeCircleBean
java 复制代码
public class ExtLifeCircleBean implements InitializingBean, DisposableBean {
    private DependencyBean dependencyBean;

    // 1. 实例化:构造函数
    public ExtLifeCircleBean() {
        System.out.println("【1. 实例化】ExtLifeCircleBean - 调用构造函数");
    }

    // 2. 属性填充:通过setter方法进行依赖注入
    @Autowired
    public void setDependencyBean(DependencyBean dependencyBean) {
        System.out.println("【2. 属性填充】ExtLifeCircleBean - 依赖注入 (setDependencyBean)");
        this.dependencyBean = dependencyBean;
    }

    // 3. 初始化方法
    // 3.1 @PostConstruct
    @PostConstruct
    public void postConstruct() {
        System.out.println("【3.1 初始化】ExtLifeCircleBean - @PostConstruct注解方法被调用");
    }
    // 3.2 InitializingBean.afterPropertiesSet
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("【3.2 初始化】ExtLifeCircleBean - 实现InitializingBean.afterPropertiesSet方法被调用");
    }

    // 3.3 Bean指定init-method
    public void init() {
        System.out.println("【3.3 初始化】ExtLifeCircleBean - 自定义init-method被调用");
        System.out.println("在init()中,dependencyBean 是否为空? " + (dependencyBean == null));
    }

    // 4. 销毁方法
    // 4.1 @PreDestroy
    @PreDestroy
    public void preDestroy() {
        System.out.println("【4.1 销毁】ExtLifeCircleBean - @PreDestroy注解方法被调用");
    }

    // 4.2 DisposableBean.destroy
    @Override
    public void destroy() throws Exception {
        System.out.println("【4.2 销毁】ExtLifeCircleBean - 实现DisposableBean.destroy方法被调用");
    }
    // 4.3 @Bean 指定 destroy-method
    public void annoDestroy() {
        System.out.println("【4.3 销毁】ExtLifeCircleBean - 自定义destroy-method被调用");
    }

    public void doSomething() {
        System.out.println("ExtLifeCircleBean is doing something");
    }
}
  1. 配置类
java 复制代码
@Configuration
public class LifeCircleConfig {
    @Bean
    public DependencyBean dependencyBean() {
        return new DependencyBean();
    }


    @Bean(initMethod = "init", destroyMethod = "annoDestroy")
    public ExtLifeCircleBean extLifeCircleBean() {
        return new ExtLifeCircleBean();
    }
}
  1. 测试代码:
java 复制代码
public class LifeCircleTest {
    public static void main(String[] args) {
        // 开始创建Spring应用上下文
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(LifeCircleConfig.class);

        ExtLifeCircleBean service = context.getBean(ExtLifeCircleBean.class);
        service.doSomething();

        // 关闭容器
        context.close();
    }
}

输出结果:

console 复制代码
【1. 实例化】ExtLifeCircleBean - 调用构造函数
【2. 属性填充】ExtLifeCircleBean - 依赖注入 (setDependencyBean)
【3.1 初始化】ExtLifeCircleBean - @PostConstruct注解方法被调用
【3.2 初始化】ExtLifeCircleBean - 实现InitializingBean.afterPropertiesSet方法被调用
【3.3 初始化】ExtLifeCircleBean - 自定义init-method被调用
在init()中,dependencyBean 是否为空? false
ExtLifeCircleBean is doing something
【4.1 销毁】ExtLifeCircleBean - @PreDestroy注解方法被调用
【4.2 销毁】ExtLifeCircleBean - 实现DisposableBean.destroy方法被调用
【4.3 销毁】ExtLifeCircleBean - 自定义destroy-method被调用

加入标准注解后的生命周期顺序如下所示:

flowchart TD A["实例化: 调用构造函数"] --> B["属性填充: 依赖注入"] B --> C[初始化] subgraph C [初始化序列] C1["@PostConstruct 方法"] C2["InitializingBean.afterPropertiesSet"] C3["自定义 init-method"] end C --> D[Bean 准备就绪] D --> E[容器关闭] E --> F[销毁] subgraph F [销毁序列] F1["@PreDestroy 方法"] F2[DisposableBean.destroy] F3[自定义 destroy-method] end

四、最强大的扩展点:BeanPostProcessor

BeanPostProcessor 是 Spring 框架中最强大、最核心的扩展点之一 。它是一个"后处理器",它有两个方法,允许你在 Bean 实例化、依赖注入完成后,在初始化回调(如 InitializingBean, @PostConstruct) 执行之前之后,对 Bean 本身进行干预。

工作原理 :Spring 容器在创建每个 Bean 时,都会遍历应用上下文中所有实现了 BeanPostProcessor 接口的 Bean,并依次调用它们的方法。这是一个典型的责任链模式

BeanPostProcessor 中定义的2个方法:

java 复制代码
public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
    }

    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
    }
}

1. Spring 框架内 BeanPostProcessor 的广泛应用

Spring 框架内部大量使用了 BeanPostProcessor 来实现关键功能:

  • AutowiredAnnotationBeanPostProcessor

    • 功能 :负责处理 @Autowired@Value 注解。
    • 时机 :主要在 postProcessProperties() (这是 InstantiationAwareBeanPostProcessor 接口的方法,是 BeanPostProcessor 的子接口) 中完成依赖注入。
  • CommonAnnotationBeanPostProcessor

    • 功能 :处理 Java 标准注解,如 @PostConstruct, @PreDestroy, @Resource
    • 时机@PostConstruct 方法的发现和注册是在后处理阶段完成的。
  • ApplicationContextAwareProcessor

    • 功能 :负责回调各种 *Aware 接口(如 ApplicationContextAware, BeanNameAware, EnvironmentAware)。
    • 时机 :在 postProcessBeforeInitialization 中检查并调用这些 Aware 接口的方法,为 Bean 注入容器相关信息。

2. 自定义 BeanPostProcessor 实现日志打印

代码示例:实现一个方法调用日志处理器

java 复制代码
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof ExtLifeCircleBean) {
            System.out.println("【3 之前】LoggingBeanPostProcessor.postProcessBeforeInitialization... before...");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof ExtLifeCircleBean) { // 只为特定类创建代理对象
            System.out.println("【3 之后】LoggingBeanPostProcessor.postProcessAfterInitialization... after...");
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(bean.getClass());
            enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
                System.out.println(">>>>>>>>>>>>>> CGLIB Proxy - Before: " + method.getName());
                // 调用原始方法
                Object result = proxy.invokeSuper(obj, args);
                System.out.println(">>>>>>>>>>>>>> CGLIB Proxy - After: " + method.getName());
                return result;
            });
            return enhancer.create();
        }
        // 如果不是特定的类,直接返回原始Bean
        return bean;
    }
}

修改配置类代码:增加@ComponentScan(basePackages = "xxx.xxx.xxx")

java 复制代码
@Configuration
@ComponentScan(basePackages = "xxx.xxx.xxx")
public class LifeCircleConfig {
    // 其他不变
}

输出结果:

console 复制代码
【1. 实例化】ExtLifeCircleBean - 调用构造函数
【2. 属性填充】ExtLifeCircleBean - 依赖注入 (setDependencyBean)
【3 之前】LoggingBeanPostProcessor.postProcessBeforeInitialization... before...
【3.1 初始化】ExtLifeCircleBean - @PostConstruct注解方法被调用
【3.2 初始化】ExtLifeCircleBean - 实现InitializingBean.afterPropertiesSet方法被调用
【3.3 初始化】ExtLifeCircleBean - 自定义init-method被调用
在init()中,dependencyBean 是否为空? false
【3 之后】LoggingBeanPostProcessor.postProcessAfterInitialization... after...
【1. 实例化】ExtLifeCircleBean - 调用构造函数
>>>>>>>>>>>>>> CGLIB Proxy - Before: doSomething
ExtLifeCircleBean is doing something
>>>>>>>>>>>>>> CGLIB Proxy - After: doSomething
【4.1 销毁】ExtLifeCircleBean - @PreDestroy注解方法被调用
【4.2 销毁】ExtLifeCircleBean - 实现DisposableBean.destroy方法被调用
【4.3 销毁】ExtLifeCircleBean - 自定义destroy-method被调用

从结果可以看到在调用doSomething方法前后都打印了日志,说明定义的日志处理器生效了,并且从Spring容器中拿到的是一个代理对象而不再是一个原始对象了,其类型为ExtLifeCircleBean$$EnhancerByCGLIB$$c8380ae5

从结果中还可以看到打印了2次"【1. 实例化】ExtLifeCircleBean - 调用构造函数",这是因为 CGLIB 通过继承机制创建代理,而 Java 中的继承机制要求子类构造方法必须调用父类构造方法。

3. 执行顺序的重要性

多个 BeanPostProcessor 的执行顺序非常关键。Spring 允许通过实现 Ordered 接口或使用 @Order 注解来定义顺序。例如,AutowiredAnnotationBeanPostProcessor 必须在你的自定义后处理器之前执行,否则依赖注入可能无法正常工作。

4. 最佳实践

  • 轻量操作 :在 BeanPostProcessor 中应保持逻辑轻量,因为它会影响每一个 Bean 的创建过程。
  • 避免代理链 :谨慎地在 BeanPostProcessor 中创建代理,多个后处理器可能会创建多层代理,影响性能和调试。

总结

以上是关于Spring Bean生命周期的全部内容,首先是4个基本的生命周期阶段,然后分别讲了Spring官方接口扩展点和JDK标准注解扩展点,最后讲了BeanPostProcessor实现全局扩展。

通过理解 Spring Bean 的生命周期,是从"会用 Spring"到"懂 Spring"的关键一步。它不再是黑盒,而是一个提供了丰富扩展点的标准化流程。我们可以通过实现接口、使用注解,特别是利用强大的 BeanPostProcessor,在生命周期的各个阶段注入自定义逻辑,从而实现诸如事务管理、AOP、监控、安全等高级功能。

最后,让我们用一张完整的流程图来回顾并总结本文的内容:

flowchart TD A["实例化: 调用构造函数"] --> B["属性填充: 依赖注入"] B --> C[BeanPostProcessor
前置处理] C --> D[初始化] subgraph D [初始化序列] D1["@PostConstruct 方法"] D2["InitializingBean.afterPropertiesSet"] D3["自定义 init-method"] end D --> E["BeanPostProcessor 后置处理
(AOP在此创建代理)"] E --> F[Bean 准备就绪] F --> G[容器关闭] G --> H[销毁] subgraph H [销毁序列] H1["@PreDestroy 方法"] H2[DisposableBean.destroy] H3["自定义 destroy-method"] end
相关推荐
little_xianzhong2 小时前
步骤流程中日志记录方案(类aop)
java·开发语言
半桔2 小时前
【STL源码剖析】二叉世界的平衡:从BST 到 AVL-tree 和 RB-tree 的插入逻辑
java·数据结构·c++·算法·set·map
用户3721574261353 小时前
Python 轻松实现替换或修改 PDF 文字
java
用户6083089290473 小时前
Java中的接口(Interface)与抽象类(Abstract Class)
java·后端
前行的小黑炭3 小时前
Android LayoutInflater 是什么?XML到View的过程
android·java·kotlin
尚久龙3 小时前
安卓学习 之 SeekBar(音视频播放进度条)
android·java·学习·手机·android studio
要一起看日出3 小时前
Shiro概述
java·spring boot·java-ee
不秃的开发媛3 小时前
Java开发入门指南:IDE选择与数据库连接详解
java·数据库·ide