前言
为什么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实例。
执行流程
- 容器启动时,扫描
@Component、@Service、@Controller、@Bean等注解标注的类/方法,解析生成对应的BeanDefinition - 将所有
BeanDefinition注册到BeanDefinitionRegistry(Bean定义注册中心) - 执行所有
BeanFactoryPostProcessor实现类,允许开发者在Bean实例化之前,修改、新增、删除BeanDefinition
核心特性
BeanFactoryPostProcessor是容器级别的扩展点,执行时机在所有Bean实例化之前,整个容器生命周期只执行一次,可对Bean的元数据进行批量修改。
实战示例:自定义BeanFactoryPostProcessor动态修改Bean定义
pom.xml核心依赖:
<?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实现类:
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
public class 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#createBean → doCreateBean → createBeanInstance
核心逻辑
- 先检查单例池
singletonObjects中是否已存在该Bean,存在则直接返回 - 不存在则通过反射机制,匹配并调用Bean的构造方法,生成一个裸对象(仅完成实例化,未进行属性填充和初始化,所有属性均为默认值)
- 实例化完成后,属性填充之前,会将该Bean的早期对象工厂
ObjectFactory放入三级缓存,为解决循环依赖提供支撑
关键注意点
实例化只是完成了JVM层面的对象创建,此时的对象还未完成依赖注入,无法正常使用,很多开发者在构造方法中调用依赖Bean的方法,会出现空指针异常,根本原因就是构造方法执行时,属性填充还未开始。
阶段3:属性填充(Populate)
实例化完成后,容器进入属性填充阶段,也就是我们常说的依赖注入(DI)。
底层源码对应
AbstractAutowireCapableBeanFactory#populateBean
核心逻辑
- 解析Bean中所有标注了
@Autowired、@Value、@Resource等注解的属性/setter方法 - 从容器中查找匹配的依赖Bean,若依赖Bean未创建,会先触发依赖Bean的完整生命周期
- 通过反射机制,完成属性的赋值操作
关键特性
Spring的依赖注入只会处理容器管理的Bean,手动通过new创建的对象,其标注了@Autowired的属性不会被容器注入,这是生产中常见的空指针诱因。
示例:验证实例化与属性填充的执行顺序
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")
public class 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已完成注入");
}
}
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标准)
BeanNameAware:注入当前Bean在容器中的名称BeanClassLoaderAware:注入加载当前Bean的ClassLoaderBeanFactoryAware:注入当前的BeanFactory实例EnvironmentAware:注入Environment环境配置对象EmbeddedValueResolverAware:注入占位符解析器,用于解析${}配置ResourceLoaderAware:注入资源加载器(仅ApplicationContext环境生效)ApplicationEventPublisherAware:注入事件发布器MessageSourceAware:注入国际化消息源ApplicationContextAware:注入ApplicationContext应用上下文对象
示例:Aware接口回调顺序验证
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
public class 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方法,也就是初始化前置处理。
核心定义
BeanPostProcessor是Bean级别的扩展点,对容器中所有的Bean生效,在Bean初始化之前执行,允许开发者对Bean实例进行修改、包装、替换。
底层源码对应
AbstractAutowireCapableBeanFactory#initializeBean → applyBeanPostProcessorsBeforeInitialization
核心特性
Spring大量核心功能基于此扩展点实现:
@PostConstruct注解的处理,由CommonAnnotationBeanPostProcessor的前置处理方法完成@Autowired注解的属性注入,由AutowiredAnnotationBeanPostProcessor完成- 数据校验、参数转换等功能,均基于此扩展点实现
示例:自定义BeanPostProcessor前置处理
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
public class 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提供了三种实现方式,执行顺序固定不可更改。
固定执行顺序
- 标注了
@PostConstruct注解的方法 - 实现了
InitializingBean接口的afterPropertiesSet方法 - Bean定义中指定的
init-method方法(@Bean(initMethod = "xxx"))
顺序底层逻辑
@PostConstruct由CommonAnnotationBeanPostProcessor的前置处理方法执行,而前置处理在初始化操作之前,因此最先执行;afterPropertiesSet在invokeInitMethods方法中优先执行,之后才会执行自定义的init-method方法。
底层源码对应
AbstractAutowireCapableBeanFactory#invokeInitMethods
示例:三种初始化方式执行顺序验证
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
public class 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方法执行");
}
}
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
public class BeanConfig {
@Bean(initMethod = "customInit", name = "initDemoBean")
public InitDemoBean initDemoBean() {
return new InitDemoBean();
}
}
运行后控制台会严格按照上述顺序打印初始化日志,100%匹配源码逻辑。
阶段7:BeanPostProcessor后置处理
初始化操作完成后,容器会执行所有BeanPostProcessor实现类的postProcessAfterInitialization方法,也就是初始化后置处理。
核心作用
在Bean初始化完成后,对Bean进行最终的修改、包装、替换,Spring AOP的动态代理对象,就是在这个阶段生成的!这是Spring最核心的特性之一。
底层源码对应
AbstractAutowireCapableBeanFactory#initializeBean → applyBeanPostProcessorsAfterInitialization
关键特性
如果Bean被AOP切面拦截,在后置处理阶段,会返回一个动态代理对象替换原始的目标对象,最终存入单例池的是代理对象,而非原始对象,这也是AOP能够实现功能增强的底层原理。
阶段8:Bean就绪,存入单例池
后置处理完成后,Bean的完整创建流程就结束了,此时的Bean已经是完全初始化完成、可正常使用的成熟对象。
- 单例Bean:会被存入Spring IoC容器的单例池
singletonObjects(一个ConcurrentHashMap)中,后续所有获取该Bean的请求,都会直接从单例池中返回,不会重复创建 - 原型Bean:不会存入单例池,每次调用
getBean()都会重新执行完整的生命周期,创建新的实例
阶段9:Bean销毁流程
当Spring容器关闭时(调用ConfigurableApplicationContext#close()方法),会触发单例Bean的销毁流程,原型Bean不会被容器管理销毁,由JVM垃圾回收处理。
固定执行顺序
- 标注了
@PreDestroy注解的方法 - 实现了
DisposableBean接口的destroy方法 - Bean定义中指定的
destroy-method方法(@Bean(destroyMethod = "xxx"))
底层源码对应
DefaultSingletonBeanRegistry#destroySingleton → DisposableBeanAdapter#destroy
示例:三种销毁方式执行顺序验证
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
public class 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方法执行");
}
}
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
public class DestroyBeanConfig {
@Bean(destroyMethod = "customDestroy", name = "destroyDemoBean")
public DestroyDemoBean destroyDemoBean() {
return new DestroyDemoBean();
}
}
容器关闭时,控制台会严格按照上述顺序打印销毁日志,可用于生产环境的优雅停机、资源释放等场景。
四、生产场景实战落地
实战1:基于InitializingBean实现系统启动资源预热
生产场景:系统启动时,将数据库中的字典数据、系统配置加载到本地内存,避免每次接口请求都查询数据库,大幅提升接口响应速度。
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
public class SysDictService implements InitializingBean {
/**
* 本地内存缓存,存储字典数据
*/
private final 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:
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 = "系统字典实体")
public class SysDict implements Serializable {
private static final long 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;
}
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建表语句:
CREATE TABLE `sys_dict` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`dict_code` varchar(100) NOT NULL COMMENT '字典编码',
`dict_value` varchar(500) NOT NULL COMMENT '字典值',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用 1-启用',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_dict_code` (`dict_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统字典表';
实战2:基于BeanPostProcessor实现全局接口日志切面
生产场景:替代传统AOP,实现所有Controller接口的入参、出参、响应时间全量日志打印,性能更优,扩展更灵活。
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
public class 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示例:
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 = "系统字典查询相关接口")
public class 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);
}
}
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实现优雅停机资源释放
生产场景:容器关闭时,优雅关闭线程池、释放数据库连接、持久化内存数据,避免强制停机导致的数据丢失、资源泄漏问题。
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
public class ThreadPoolService implements DisposableBean {
/**
* 业务处理线程池
*/
private final 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,核心原因与解决方案
核心原因(结合生命周期)
- Bean未被容器管理:通过
new手动创建的对象,不会被Spring扫描,容器不会执行属性填充,导致注入为null - 静态属性注入:
@Autowired只能注入实例属性,静态属性属于类,容器不会在实例化阶段注入静态属性 - 初始化顺序错误:在构造方法、静态代码块中调用依赖Bean的方法,此时属性填充还未执行,属性为null
- Bean循环依赖:构造器注入的循环依赖,容器无法完成实例化,导致属性注入失败
解决方案
- 所有需要依赖注入的Bean,必须通过
@Component等注解交给Spring容器管理,禁止手动new - 静态属性注入需通过setter方法注入,将注解标注在setter方法上
- 初始化逻辑必须放在
@PostConstruct或afterPropertiesSet方法中,禁止在构造方法中调用依赖Bean - 循环依赖场景使用setter注入替代构造器注入,开启懒加载
问题2:Spring为什么能解决setter注入的循环依赖,无法解决构造器注入的循环依赖?
结合生命周期的底层原理
Spring通过三级缓存解决循环依赖,三级缓存的放入时机是实例化完成后,属性填充之前:
- setter注入循环依赖:A依赖B,B依赖A。A先完成实例化,将早期对象工厂放入三级缓存,然后执行属性填充,需要注入B;触发B的生命周期,B实例化后属性填充需要注入A,从三级缓存中获取A的早期对象,完成B的初始化;A再完成后续的属性填充和初始化,循环依赖解决。
- 构造器注入循环依赖:A的构造方法需要B,B的构造方法需要A。A在实例化阶段(调用构造方法)就需要B,此时A还未完成实例化,无法放入三级缓存;触发B的实例化,B的构造方法需要A,此时A还未创建,容器无法找到A的实例,直接抛出循环依赖异常。
问题3:自定义的BeanPostProcessor不生效,核心原因与解决方案
核心原因
- BeanPostProcessor未被容器扫描到:没有标注
@Component注解,或不在Spring的扫描路径下 - 循环依赖导致提前初始化:BeanPostProcessor被其他Bean依赖,导致其在容器早期就被初始化,无法对后续的Bean生效
- 被AOP代理导致失效:BeanPostProcessor本身被AOP代理,破坏了容器的扩展点执行逻辑
- 静态内部类未使用static修饰:非静态内部类无法被Spring实例化,导致扩展点不生效
解决方案
- 确保BeanPostProcessor实现类标注
@Component注解,且在Spring的扫描路径内 - BeanPostProcessor禁止被其他业务Bean依赖,保持独立的容器扩展角色
- 禁止对BeanPostProcessor实现类进行AOP切面拦截
- 内部类实现的BeanPostProcessor必须使用static修饰
七、总结
Spring Bean的生命周期,是Spring IoC容器的核心灵魂,所有的Spring高级特性、扩展点、生产问题,都离不开生命周期的底层逻辑。 本文从最基础的概念定义,到全流程的源码级拆解,再到生产场景的实战落地,最后到高频问题的排查解决,完整覆盖了Bean生命周期的所有核心知识点。