首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >吃透 Spring Bean 生命周期:从源码底层到实战落地

吃透 Spring Bean 生命周期:从源码底层到实战落地

作者头像
果酱带你啃java
发布2026-04-14 15:00:45
发布2026-04-14 15:00:45
600
举报

前言

为什么Spring能成为Java企业级开发的事实标准?核心就是IoC(控制反转)与AOP(面向切面编程)两大特性,而Bean的生命周期,正是IoC容器的核心灵魂。 很多开发者用了多年Spring,却只停留在@Autowired注入Bean的表层,一旦遇到Bean初始化顺序异常、循环依赖报错、扩展点执行不符合预期、启动时资源加载失败等问题,就无从下手;面试时被问到生命周期,也只能背出几个零散的阶段,无法串联底层逻辑。 本文基于Spring Framework 6.2.3(Spring Boot 3.4.4) ,从底层源码、执行流程、扩展点实战、生产问题排查全维度,用通俗的语言讲透Bean生命周期的完整逻辑,所有示例均可直接编译运行,兼顾入门理解与深度进阶,帮你彻底打通Spring的任督二脉。

一、前置认知:Bean与普通Java对象的核心区别

很多人入门时会混淆:Bean不就是Java对象吗?为什么要单独讲生命周期? 这里先给出100%准确的定义:

  • 普通Java对象:通过new关键字手动实例化,对象的创建、属性赋值、销毁全由开发者自己控制,生命周期仅与JVM的垃圾回收相关。
  • Spring Bean:由Spring IoC容器统一管理的对象,容器会按照固定的流程,完成Bean的实例化、属性注入、初始化、销毁全生命周期管控,同时提供了大量可插拔的扩展点,允许开发者在生命周期的任意节点介入,定制Bean的行为。

一句话总结:普通对象的命运握在开发者手里,Bean的命运握在Spring容器手里。

二、Bean生命周期完整总览

先给出Spring 6.x标准的Bean生命周期全流程流程图,执行顺序100%匹配源码逻辑:

其中初始化操作的子流程与销毁操作的子流程,也有固定的执行顺序,后续会分阶段深度拆解。

三、生命周期全阶段深度拆解

本章节所有源码均对应Spring Framework 6.2.3的AbstractAutowireCapableBeanFactory核心类,所有示例均基于JDK 17编写。

阶段1:BeanDefinition扫描、注册与BeanFactoryPostProcessor扩展

容器启动的第一步,不是直接创建Bean,而是解析并注册Bean的元数据定义

核心概念

BeanDefinition是Bean的元数据载体,相当于Bean的“出生证明”,包含了Bean的全量信息:全类名、作用域、是否懒加载、依赖关系、初始化方法、销毁方法等。Spring容器完全基于这个元数据来创建Bean实例。

执行流程
  1. 容器启动时,扫描@Component@Service@Controller@Bean等注解标注的类/方法,解析生成对应的BeanDefinition
  2. 将所有BeanDefinition注册到BeanDefinitionRegistry(Bean定义注册中心)
  3. 执行所有BeanFactoryPostProcessor实现类,允许开发者在Bean实例化之前,修改、新增、删除BeanDefinition
核心特性

BeanFactoryPostProcessor容器级别的扩展点,执行时机在所有Bean实例化之前,整个容器生命周期只执行一次,可对Bean的元数据进行批量修改。

实战示例:自定义BeanFactoryPostProcessor动态修改Bean定义

pom.xml核心依赖:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.4</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>bean-lifecycle-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>bean-lifecycle-demo</name>
    <properties>
        <java.version>17</java.version>
        <lombok.version>1.18.30</lombok.version>
        <fastjson2.version>2.0.53</fastjson2.version>
        <guava.version>33.2.1-jre</guava.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <mysql.version>8.4.0</mysql.version>
        <springdoc.version>2.6.0</springdoc.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

自定义BeanFactoryPostProcessor实现类:

代码语言:javascript
复制
package com.jam.demo.processor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

/**
 * 自定义BeanFactoryPostProcessor,实现BeanDefinition动态修改
 * @author ken
 */
@Slf4j
@Component
publicclass CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    /**
     * 容器加载所有BeanDefinition之后,Bean实例化之前执行
     * @param beanFactory 可配置的Bean工厂
     * @throws BeansException 处理过程中抛出的异常
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.info("【BeanFactoryPostProcessor】执行开始,容器中已注册的BeanDefinition数量:{}", beanFactory.getBeanDefinitionCount());
        // 获取指定Bean的定义信息
        String beanName = "lifecycleDemoBean";
        if (beanFactory.containsBeanDefinition(beanName)) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            // 修改Bean为懒加载模式
            beanDefinition.setLazyInit(true);
            log.info("【BeanFactoryPostProcessor】修改Bean[{}]的懒加载属性为true", beanName);
        }
        log.info("【BeanFactoryPostProcessor】执行结束");
    }
}

阶段2:Bean实例化(Instantiation)

当BeanDefinition注册完成,且BeanFactoryPostProcessor执行完毕后,容器会开始创建Bean实例。

核心规则
  • 单例Bean(默认@Scope("singleton")):容器启动时完成实例化,存入单例池全局唯一
  • 原型Bean(@Scope("prototype")):每次调用getBean()时才会触发实例化,每次创建新实例
  • 懒加载Bean(@Lazy):首次被获取时才会触发实例化
底层源码对应

AbstractAutowireCapableBeanFactory#createBeandoCreateBeancreateBeanInstance

核心逻辑
  1. 先检查单例池singletonObjects中是否已存在该Bean,存在则直接返回
  2. 不存在则通过反射机制,匹配并调用Bean的构造方法,生成一个裸对象(仅完成实例化,未进行属性填充和初始化,所有属性均为默认值)
  3. 实例化完成后,属性填充之前,会将该Bean的早期对象工厂ObjectFactory放入三级缓存,为解决循环依赖提供支撑
关键注意点

实例化只是完成了JVM层面的对象创建,此时的对象还未完成依赖注入,无法正常使用,很多开发者在构造方法中调用依赖Bean的方法,会出现空指针异常,根本原因就是构造方法执行时,属性填充还未开始。

阶段3:属性填充(Populate)

实例化完成后,容器进入属性填充阶段,也就是我们常说的依赖注入(DI)

底层源码对应

AbstractAutowireCapableBeanFactory#populateBean

核心逻辑
  1. 解析Bean中所有标注了@Autowired@Value@Resource等注解的属性/setter方法
  2. 从容器中查找匹配的依赖Bean,若依赖Bean未创建,会先触发依赖Bean的完整生命周期
  3. 通过反射机制,完成属性的赋值操作
关键特性

Spring的依赖注入只会处理容器管理的Bean,手动通过new创建的对象,其标注了@Autowired的属性不会被容器注入,这是生产中常见的空指针诱因。

示例:验证实例化与属性填充的执行顺序
代码语言:javascript
复制
package com.jam.demo.bean;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 生命周期演示Bean,验证实例化与属性填充顺序
 * @author ken
 */
@Slf4j
@Component(value = "lifecycleDemoBean")
publicclass LifecycleDemoBean {

    private DependencyBean dependencyBean;

    /**
     * 构造方法:对应实例化阶段
     */
    public LifecycleDemoBean() {
        log.info("【阶段2-实例化】LifecycleDemoBean构造方法执行,此时dependencyBean是否为null:{}", dependencyBean == null);
    }

    /**
     * 依赖注入:对应属性填充阶段
     * @param dependencyBean 依赖的Bean实例
     */
    @Autowired
    public void setDependencyBean(DependencyBean dependencyBean) {
        this.dependencyBean = dependencyBean;
        log.info("【阶段3-属性填充】setDependencyBean执行,此时dependencyBean是否为null:{}", dependencyBean == null);
    }

    @PostConstruct
    public void init() {
        log.info("初始化阶段执行,dependencyBean已完成注入");
    }
}
代码语言:javascript
复制
package com.jam.demo.bean;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 依赖Bean示例
 * @author ken
 */
@Slf4j
@Component
public class DependencyBean {
    public DependencyBean() {
        log.info("DependencyBean实例化完成");
    }
}

运行后控制台会清晰打印:实例化构造方法执行时,dependencyBean为null;属性填充set方法执行时,dependencyBean已完成注入,完美验证执行顺序。

阶段4:Aware接口回调

属性填充完成后,容器会检查当前Bean是否实现了Aware系列接口,若实现了,会按固定顺序回调对应的接口方法,将Spring容器的底层组件注入到Bean中。

核心作用

Aware接口是Spring提供的容器资源注入通道,让Bean能够感知到自身在容器中的信息,以及获取容器的底层核心组件。

固定执行顺序(Spring 6.x标准)
  1. BeanNameAware:注入当前Bean在容器中的名称
  2. BeanClassLoaderAware:注入加载当前Bean的ClassLoader
  3. BeanFactoryAware:注入当前的BeanFactory实例
  4. EnvironmentAware:注入Environment环境配置对象
  5. EmbeddedValueResolverAware:注入占位符解析器,用于解析${}配置
  6. ResourceLoaderAware:注入资源加载器(仅ApplicationContext环境生效)
  7. ApplicationEventPublisherAware:注入事件发布器
  8. MessageSourceAware:注入国际化消息源
  9. ApplicationContextAware:注入ApplicationContext应用上下文对象
示例:Aware接口回调顺序验证
代码语言:javascript
复制
package com.jam.demo.bean;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
 * Aware接口回调顺序验证Bean
 * @author ken
 */
@Slf4j
@Component
publicclass AwareDemoBean implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware,
        EnvironmentAware, ApplicationContextAware {

    /**
     * 回调Bean名称
     * @param beanName 当前Bean在容器中的名称
     */
    @Override
    public void setBeanName(String beanName) {
        log.info("【阶段4-Aware回调】1.BeanNameAware.setBeanName执行,beanName:{}", beanName);
    }

    /**
     * 回调类加载器
     * @param classLoader 加载当前Bean的类加载器
     */
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        log.info("【阶段4-Aware回调】2.BeanClassLoaderAware.setBeanClassLoader执行");
    }

    /**
     * 回调Bean工厂
     * @param beanFactory 当前Bean所属的BeanFactory
     * @throws BeansException 异常
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("【阶段4-Aware回调】3.BeanFactoryAware.setBeanFactory执行");
    }

    /**
     * 回调环境配置
     * @param environment 环境配置对象
     */
    @Override
    public void setEnvironment(Environment environment) {
        log.info("【阶段4-Aware回调】4.EnvironmentAware.setEnvironment执行");
    }

    /**
     * 回调应用上下文
     * @param applicationContext 应用上下文对象
     * @throws BeansException 异常
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("【阶段4-Aware回调】5.ApplicationContextAware.setApplicationContext执行");
    }
}

运行后控制台会严格按照上述顺序打印回调日志,无任何偏差。

阶段5:BeanPostProcessor前置处理

Aware接口回调完成后,容器会执行所有BeanPostProcessor实现类的postProcessBeforeInitialization方法,也就是初始化前置处理

核心定义

BeanPostProcessorBean级别的扩展点,对容器中所有的Bean生效,在Bean初始化之前执行,允许开发者对Bean实例进行修改、包装、替换。

底层源码对应

AbstractAutowireCapableBeanFactory#initializeBeanapplyBeanPostProcessorsBeforeInitialization

核心特性

Spring大量核心功能基于此扩展点实现:

  • @PostConstruct注解的处理,由CommonAnnotationBeanPostProcessor的前置处理方法完成
  • @Autowired注解的属性注入,由AutowiredAnnotationBeanPostProcessor完成
  • 数据校验、参数转换等功能,均基于此扩展点实现
示例:自定义BeanPostProcessor前置处理
代码语言:javascript
复制
package com.jam.demo.processor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * 自定义BeanPostProcessor实现类
 * @author ken
 */
@Slf4j
@Component
publicclass CustomBeanPostProcessor implements BeanPostProcessor {

    /**
     * Bean初始化之前执行
     * @param bean 当前Bean实例
     * @param beanName 当前Bean名称
     * @return 处理后的Bean实例(可替换原实例)
     * @throws BeansException 处理异常
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 仅处理我们的演示Bean,避免全量Bean打印日志过多
        if ("lifecycleDemoBean".equals(beanName) || "awareDemoBean".equals(beanName)) {
            log.info("【阶段5-前置处理】BeanPostProcessor.before执行,beanName:{}", beanName);
        }
        return bean;
    }

    /**
     * Bean初始化之后执行
     * @param bean 当前Bean实例
     * @param beanName 当前Bean名称
     * @return 处理后的Bean实例(可替换原实例)
     * @throws BeansException 处理异常
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("lifecycleDemoBean".equals(beanName) || "awareDemoBean".equals(beanName)) {
            log.info("【阶段7-后置处理】BeanPostProcessor.after执行,beanName:{}", beanName);
        }
        return bean;
    }
}

阶段6:Bean初始化操作

前置处理完成后,进入Bean的核心初始化阶段,开发者可在此阶段实现自定义的初始化逻辑,Spring提供了三种实现方式,执行顺序固定不可更改

固定执行顺序
  1. 标注了@PostConstruct注解的方法
  2. 实现了InitializingBean接口的afterPropertiesSet方法
  3. Bean定义中指定的init-method方法(@Bean(initMethod = "xxx")
顺序底层逻辑

@PostConstructCommonAnnotationBeanPostProcessor的前置处理方法执行,而前置处理在初始化操作之前,因此最先执行;afterPropertiesSetinvokeInitMethods方法中优先执行,之后才会执行自定义的init-method方法。

底层源码对应

AbstractAutowireCapableBeanFactory#invokeInitMethods

示例:三种初始化方式执行顺序验证
代码语言:javascript
复制
package com.jam.demo.bean;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

/**
 * 初始化方式顺序验证Bean
 * @author ken
 */
@Slf4j
@Component
publicclass InitDemoBean implements InitializingBean {

    /**
     * 第一种初始化方式:@PostConstruct注解
     */
    @PostConstruct
    public void postConstructInit() {
        log.info("【阶段6-初始化】1.@PostConstruct注解方法执行");
    }

    /**
     * 第二种初始化方式:InitializingBean接口实现
     * @throws Exception 初始化异常
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("【阶段6-初始化】2.InitializingBean.afterPropertiesSet方法执行");
    }

    /**
     * 第三种初始化方式:自定义init-method方法
     * 需在@Bean注解中指定initMethod = "customInit"
     */
    public void customInit() {
        log.info("【阶段6-初始化】3.自定义init-method方法执行");
    }
}
代码语言:javascript
复制
package com.jam.demo.config;

import com.jam.demo.bean.InitDemoBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Bean配置类,指定init-method
 * @author ken
 */
@Configuration
publicclass BeanConfig {

    @Bean(initMethod = "customInit", name = "initDemoBean")
    public InitDemoBean initDemoBean() {
        returnnew InitDemoBean();
    }
}

运行后控制台会严格按照上述顺序打印初始化日志,100%匹配源码逻辑。

阶段7:BeanPostProcessor后置处理

初始化操作完成后,容器会执行所有BeanPostProcessor实现类的postProcessAfterInitialization方法,也就是初始化后置处理

核心作用

在Bean初始化完成后,对Bean进行最终的修改、包装、替换,Spring AOP的动态代理对象,就是在这个阶段生成的!这是Spring最核心的特性之一。

底层源码对应

AbstractAutowireCapableBeanFactory#initializeBeanapplyBeanPostProcessorsAfterInitialization

关键特性

如果Bean被AOP切面拦截,在后置处理阶段,会返回一个动态代理对象替换原始的目标对象,最终存入单例池的是代理对象,而非原始对象,这也是AOP能够实现功能增强的底层原理。

阶段8:Bean就绪,存入单例池

后置处理完成后,Bean的完整创建流程就结束了,此时的Bean已经是完全初始化完成、可正常使用的成熟对象。

  • 单例Bean:会被存入Spring IoC容器的单例池singletonObjects(一个ConcurrentHashMap)中,后续所有获取该Bean的请求,都会直接从单例池中返回,不会重复创建
  • 原型Bean:不会存入单例池,每次调用getBean()都会重新执行完整的生命周期,创建新的实例

阶段9:Bean销毁流程

当Spring容器关闭时(调用ConfigurableApplicationContext#close()方法),会触发单例Bean的销毁流程,原型Bean不会被容器管理销毁,由JVM垃圾回收处理。

固定执行顺序
  1. 标注了@PreDestroy注解的方法
  2. 实现了DisposableBean接口的destroy方法
  3. Bean定义中指定的destroy-method方法(@Bean(destroyMethod = "xxx")
底层源码对应

DefaultSingletonBeanRegistry#destroySingletonDisposableBeanAdapter#destroy

示例:三种销毁方式执行顺序验证
代码语言:javascript
复制
package com.jam.demo.bean;

import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;

/**
 * 销毁方式顺序验证Bean
 * @author ken
 */
@Slf4j
@Component
publicclass DestroyDemoBean implements DisposableBean {

    /**
     * 第一种销毁方式:@PreDestroy注解
     */
    @PreDestroy
    public void preDestroy() {
        log.info("【阶段9-销毁】1.@PreDestroy注解方法执行");
    }

    /**
     * 第二种销毁方式:DisposableBean接口实现
     * @throws Exception 销毁异常
     */
    @Override
    public void destroy() throws Exception {
        log.info("【阶段9-销毁】2.DisposableBean.destroy方法执行");
    }

    /**
     * 第三种销毁方式:自定义destroy-method方法
     * 需在@Bean注解中指定destroyMethod = "customDestroy"
     */
    public void customDestroy() {
        log.info("【阶段9-销毁】3.自定义destroy-method方法执行");
    }
}
代码语言:javascript
复制
package com.jam.demo.config;

import com.jam.demo.bean.DestroyDemoBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Bean配置类,指定destroy-method
 * @author ken
 */
@Configuration
publicclass DestroyBeanConfig {

    @Bean(destroyMethod = "customDestroy", name = "destroyDemoBean")
    public DestroyDemoBean destroyDemoBean() {
        returnnew DestroyDemoBean();
    }
}

容器关闭时,控制台会严格按照上述顺序打印销毁日志,可用于生产环境的优雅停机、资源释放等场景。

四、生产场景实战落地

实战1:基于InitializingBean实现系统启动资源预热

生产场景:系统启动时,将数据库中的字典数据、系统配置加载到本地内存,避免每次接口请求都查询数据库,大幅提升接口响应速度。

代码语言:javascript
复制
package com.jam.demo.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Maps;
import com.jam.demo.entity.SysDict;
import com.jam.demo.mapper.SysDictMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;

/**
 * 系统字典服务,启动时预热字典数据到内存
 * @author ken
 */
@Slf4j
@Service
publicclass SysDictService implements InitializingBean {

    /**
     * 本地内存缓存,存储字典数据
     */
    privatefinal Map<String, String> DICT_CACHE = Maps.newConcurrentMap();

    @Autowired
    private SysDictMapper sysDictMapper;

    /**
     * 系统启动时执行,加载字典数据到内存
     * @throws Exception 加载异常
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("【系统预热】开始加载字典数据到本地缓存");
        LambdaQueryWrapper<SysDict> queryWrapper = new LambdaQueryWrapper<SysDict>()
                .eq(SysDict::getStatus, 1);
        List<SysDict> dictList = sysDictMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(dictList)) {
            log.warn("【系统预热】未查询到可用的字典数据");
            return;
        }
        dictList.forEach(dict -> DICT_CACHE.put(dict.getDictCode(), dict.getDictValue()));
        log.info("【系统预热】字典数据加载完成,共加载{}条数据", DICT_CACHE.size());
    }

    /**
     * 根据字典编码获取字典值
     * @param dictCode 字典编码
     * @return 字典值
     */
    public String getDictValue(String dictCode) {
        return DICT_CACHE.get(dictCode);
    }
}

配套MyBatis-Plus实体类与Mapper:

代码语言:javascript
复制
package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;

/**
 * 系统字典实体类
 * @author ken
 */
@Data
@TableName("sys_dict")
@Schema(description = "系统字典实体")
publicclass SysDict implements Serializable {

    privatestaticfinallong serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    @Schema(description = "主键ID")
    private Long id;

    @Schema(description = "字典编码")
    private String dictCode;

    @Schema(description = "字典值")
    private String dictValue;

    @Schema(description = "状态:0-禁用 1-启用")
    private Integer status;
}
代码语言:javascript
复制
package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.SysDict;
import org.apache.ibatis.annotations.Mapper;

/**
 * 系统字典Mapper
 * @author ken
 */
@Mapper
public interface SysDictMapper extends BaseMapper<SysDict> {
}

配套MySQL建表语句:

代码语言:javascript
复制
CREATE TABLE`sys_dict` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`dict_code`varchar(100) NOTNULLCOMMENT'字典编码',
`dict_value`varchar(500) NOTNULLCOMMENT'字典值',
`status`tinyintNOTNULLDEFAULT'1'COMMENT'状态:0-禁用 1-启用',
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_dict_code` (`dict_code`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统字典表';

实战2:基于BeanPostProcessor实现全局接口日志切面

生产场景:替代传统AOP,实现所有Controller接口的入参、出参、响应时间全量日志打印,性能更优,扩展更灵活。

代码语言:javascript
复制
package com.jam.demo.processor;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Proxy;

/**
 * 全局Controller日志处理器
 * @author ken
 */
@Slf4j
@Component
publicclass ControllerLogPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 仅处理标注了@RestController的Bean
        if (bean.getClass().isAnnotationPresent(RestController.class)) {
            log.info("【日志增强】为Controller[{}]创建日志代理对象", beanName);
            // 生成JDK动态代理对象,实现接口日志打印
            return Proxy.newProxyInstance(
                    bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (proxy, method, args) -> {
                        long startTime = System.currentTimeMillis();
                        String className = bean.getClass().getSimpleName();
                        String methodName = method.getName();
                        // 打印入参
                        log.info("【接口请求】{}.{} 入参:{}", className, methodName, JSON.toJSONString(args));
                        Object result;
                        try {
                            // 执行目标方法
                            result = method.invoke(bean, args);
                            // 打印出参
                            log.info("【接口响应】{}.{} 出参:{}", className, methodName, JSON.toJSONString(result));
                        } catch (Exception e) {
                            log.error("【接口异常】{}.{} 执行异常", className, methodName, e);
                            throw e;
                        } finally {
                            // 打印响应时间
                            long endTime = System.currentTimeMillis();
                            log.info("【接口耗时】{}.{} 执行耗时:{}ms", className, methodName, (endTime - startTime));
                        }
                        return result;
                    }
            );
        }
        return bean;
    }
}

配套Controller示例:

代码语言:javascript
复制
package com.jam.demo.controller;

import com.jam.demo.service.SysDictService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 字典查询Controller
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/dict")
@Tag(name = "系统字典接口", description = "系统字典查询相关接口")
publicclass DictController implements DictControllerApi {

    @Autowired
    private SysDictService sysDictService;

    @Override
    @GetMapping("/getByCode")
    @Operation(summary = "根据字典编码查询字典值", description = "查询已启用的字典数据")
    public String getDictByCode(
            @Parameter(description = "字典编码", required = true) @RequestParam String dictCode) {
        return sysDictService.getDictValue(dictCode);
    }
}
代码语言:javascript
复制
package com.jam.demo.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
 * 字典接口规范
 * @author ken
 */
@Tag(name = "系统字典接口", description = "系统字典查询相关接口")
public interface DictControllerApi {

    @Operation(summary = "根据字典编码查询字典值", description = "查询已启用的字典数据")
    String getDictByCode(
            @Parameter(description = "字典编码", required = true) String dictCode);
}

实战3:基于DisposableBean实现优雅停机资源释放

生产场景:容器关闭时,优雅关闭线程池、释放数据库连接、持久化内存数据,避免强制停机导致的数据丢失、资源泄漏问题。

代码语言:javascript
复制
package com.jam.demo.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Service;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 线程池管理服务,优雅停机释放资源
 * @author ken
 */
@Slf4j
@Service
publicclass ThreadPoolService implements DisposableBean {

    /**
     * 业务处理线程池
     */
    privatefinal ExecutorService businessExecutor = new ThreadPoolExecutor(
            8,
            16,
            60L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000),
            r -> new Thread(r, "business-thread-" + r.hashCode()),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    /**
     * 提交任务到线程池
     * @param task 待执行的任务
     */
    public void submitTask(Runnable task) {
        businessExecutor.submit(task);
    }

    /**
     * 容器关闭时执行,优雅关闭线程池
     * @throws Exception 关闭异常
     */
    @Override
    public void destroy() throws Exception {
        log.info("【优雅停机】开始关闭业务线程池");
        // 停止接收新任务
        businessExecutor.shutdown();
        // 等待现有任务执行完成,超时30秒强制关闭
        if (!businessExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
            log.warn("【优雅停机】线程池任务执行超时,强制关闭");
            businessExecutor.shutdownNow();
        }
        log.info("【优雅停机】业务线程池关闭完成");
    }
}

五、易混淆技术点精准辨析

1. BeanFactoryPostProcessor VS BeanPostProcessor

对比维度

BeanFactoryPostProcessor

BeanPostProcessor

执行时机

所有Bean实例化之前,BeanDefinition注册完成后

Bean实例化、属性填充完成后,初始化前后

作用对象

BeanDefinition(Bean的元数据)

Bean实例(已创建的对象)

执行次数

容器生命周期内仅执行一次

每个Bean创建时都会执行两次(前置+后置)

核心用途

修改/新增/删除Bean定义,容器级别的批量配置

对Bean实例进行修改、包装、代理,Bean级别的功能增强

执行顺序

早于所有Bean的实例化,早于BeanPostProcessor

晚于BeanFactoryPostProcessor执行

2. 实例化(Instantiation) VS 初始化(Initialization)

对比维度

实例化

初始化

核心含义

JVM层面创建对象的过程,调用构造方法生成裸对象

对象创建完成后,完成属性赋值、资源加载、自定义逻辑,让对象进入可用状态

执行时机

生命周期最早期,属性填充之前

属性填充、Aware回调完成后,Bean就绪之前

对象状态

所有属性均为默认值,依赖未注入,无法正常使用

属性已完成注入,资源已加载,可正常使用

常见踩坑

在构造方法中调用依赖Bean的方法,导致空指针

在静态方法中调用实例属性,导致空指针

3. @PostConstruct VS InitializingBean VS init-method

对比维度

@PostConstruct

InitializingBean

init-method

执行顺序

最先执行

第二执行

最后执行

所属规范

JSR-250 JavaEE规范

Spring原生接口

Spring XML/注解配置

与Spring耦合

无耦合,通用Java注解

强耦合,必须实现Spring接口

无耦合,纯自定义方法

异常处理

抛出异常会终止Bean创建

抛出异常会终止Bean创建

抛出异常会终止Bean创建

推荐场景

简单初始化逻辑,无Spring耦合场景

框架级别的初始化逻辑,需要和Spring深度集成

第三方Bean的初始化,无法修改源码的场景

六、高频面试&生产问题排查解决方案

问题1:@Autowired注入的属性为null,核心原因与解决方案

核心原因(结合生命周期)
  1. Bean未被容器管理:通过new手动创建的对象,不会被Spring扫描,容器不会执行属性填充,导致注入为null
  2. 静态属性注入@Autowired只能注入实例属性,静态属性属于类,容器不会在实例化阶段注入静态属性
  3. 初始化顺序错误:在构造方法、静态代码块中调用依赖Bean的方法,此时属性填充还未执行,属性为null
  4. Bean循环依赖:构造器注入的循环依赖,容器无法完成实例化,导致属性注入失败
解决方案
  1. 所有需要依赖注入的Bean,必须通过@Component等注解交给Spring容器管理,禁止手动new
  2. 静态属性注入需通过setter方法注入,将注解标注在setter方法上
  3. 初始化逻辑必须放在@PostConstructafterPropertiesSet方法中,禁止在构造方法中调用依赖Bean
  4. 循环依赖场景使用setter注入替代构造器注入,开启懒加载

问题2:Spring为什么能解决setter注入的循环依赖,无法解决构造器注入的循环依赖?

结合生命周期的底层原理

Spring通过三级缓存解决循环依赖,三级缓存的放入时机是实例化完成后,属性填充之前

  1. setter注入循环依赖:A依赖B,B依赖A。A先完成实例化,将早期对象工厂放入三级缓存,然后执行属性填充,需要注入B;触发B的生命周期,B实例化后属性填充需要注入A,从三级缓存中获取A的早期对象,完成B的初始化;A再完成后续的属性填充和初始化,循环依赖解决。
  2. 构造器注入循环依赖:A的构造方法需要B,B的构造方法需要A。A在实例化阶段(调用构造方法)就需要B,此时A还未完成实例化,无法放入三级缓存;触发B的实例化,B的构造方法需要A,此时A还未创建,容器无法找到A的实例,直接抛出循环依赖异常。

问题3:自定义的BeanPostProcessor不生效,核心原因与解决方案

核心原因
  1. BeanPostProcessor未被容器扫描到:没有标注@Component注解,或不在Spring的扫描路径下
  2. 循环依赖导致提前初始化:BeanPostProcessor被其他Bean依赖,导致其在容器早期就被初始化,无法对后续的Bean生效
  3. 被AOP代理导致失效:BeanPostProcessor本身被AOP代理,破坏了容器的扩展点执行逻辑
  4. 静态内部类未使用static修饰:非静态内部类无法被Spring实例化,导致扩展点不生效
解决方案
  1. 确保BeanPostProcessor实现类标注@Component注解,且在Spring的扫描路径内
  2. BeanPostProcessor禁止被其他业务Bean依赖,保持独立的容器扩展角色
  3. 禁止对BeanPostProcessor实现类进行AOP切面拦截
  4. 内部类实现的BeanPostProcessor必须使用static修饰

七、总结

Spring Bean的生命周期,是Spring IoC容器的核心灵魂,所有的Spring高级特性、扩展点、生产问题,都离不开生命周期的底层逻辑。 本文从最基础的概念定义,到全流程的源码级拆解,再到生产场景的实战落地,最后到高频问题的排查解决,完整覆盖了Bean生命周期的所有核心知识点。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、前置认知:Bean与普通Java对象的核心区别
  • 二、Bean生命周期完整总览
  • 三、生命周期全阶段深度拆解
    • 阶段1:BeanDefinition扫描、注册与BeanFactoryPostProcessor扩展
      • 核心概念
      • 执行流程
      • 核心特性
      • 实战示例:自定义BeanFactoryPostProcessor动态修改Bean定义
    • 阶段2:Bean实例化(Instantiation)
      • 核心规则
      • 底层源码对应
      • 核心逻辑
      • 关键注意点
    • 阶段3:属性填充(Populate)
      • 底层源码对应
      • 核心逻辑
      • 关键特性
      • 示例:验证实例化与属性填充的执行顺序
    • 阶段4:Aware接口回调
      • 核心作用
      • 固定执行顺序(Spring 6.x标准)
      • 示例:Aware接口回调顺序验证
    • 阶段5:BeanPostProcessor前置处理
      • 核心定义
      • 底层源码对应
      • 核心特性
      • 示例:自定义BeanPostProcessor前置处理
    • 阶段6:Bean初始化操作
      • 固定执行顺序
      • 顺序底层逻辑
      • 底层源码对应
      • 示例:三种初始化方式执行顺序验证
    • 阶段7:BeanPostProcessor后置处理
      • 核心作用
      • 底层源码对应
      • 关键特性
    • 阶段8:Bean就绪,存入单例池
    • 阶段9:Bean销毁流程
      • 固定执行顺序
      • 底层源码对应
      • 示例:三种销毁方式执行顺序验证
  • 四、生产场景实战落地
    • 实战1:基于InitializingBean实现系统启动资源预热
    • 实战2:基于BeanPostProcessor实现全局接口日志切面
    • 实战3:基于DisposableBean实现优雅停机资源释放
  • 五、易混淆技术点精准辨析
    • 1. BeanFactoryPostProcessor VS BeanPostProcessor
    • 2. 实例化(Instantiation) VS 初始化(Initialization)
    • 3. @PostConstruct VS InitializingBean VS init-method
  • 六、高频面试&生产问题排查解决方案
    • 问题1:@Autowired注入的属性为null,核心原因与解决方案
      • 核心原因(结合生命周期)
      • 解决方案
    • 问题2:Spring为什么能解决setter注入的循环依赖,无法解决构造器注入的循环依赖?
      • 结合生命周期的底层原理
    • 问题3:自定义的BeanPostProcessor不生效,核心原因与解决方案
      • 核心原因
      • 解决方案
  • 七、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档