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

简介: 本文深度解析Spring 6.2.3 Bean生命周期,涵盖BeanDefinition注册、实例化、属性填充、Aware回调、BeanPostProcessor前后置处理、初始化(@PostConstruct/InitializingBean/init-method)、AOP代理、单例缓存及销毁全流程,结合源码、实战示例与生产问题排查,助你彻底掌握IoC核心机制。

前言

为什么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核心依赖:

<?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#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的属性不会被容器注入,这是生产中常见的空指针诱因。

示例:验证实例化与属性填充的执行顺序

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标准)

  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接口回调顺序验证

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方法,也就是初始化前置处理

核心定义

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

底层源码对应

AbstractAutowireCapableBeanFactory#initializeBeanapplyBeanPostProcessorsBeforeInitialization

核心特性

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提供了三种实现方式,执行顺序固定不可更改

固定执行顺序

  1. 标注了@PostConstruct注解的方法
  2. 实现了InitializingBean接口的afterPropertiesSet方法
  3. Bean定义中指定的init-method方法(@Bean(initMethod = "xxx")

顺序底层逻辑

@PostConstructCommonAnnotationBeanPostProcessor的前置处理方法执行,而前置处理在初始化操作之前,因此最先执行;afterPropertiesSetinvokeInitMethods方法中优先执行,之后才会执行自定义的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#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

示例:三种销毁方式执行顺序验证

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,核心原因与解决方案

核心原因(结合生命周期)

  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生命周期的所有核心知识点。

目录
相关文章
|
7天前
|
人工智能 运维 监控
2026年阿里云及本地搭建OpenClaw/Clawdbot实战指南:1分钟跑通5大自动化场景
2026年,OpenClaw(原Clawdbot、Moltbot)凭借“自主式工作流”特性,彻底颠覆了AI工具的使用逻辑——它不再是“问一句答一句”的交互式助手,而是能24小时后台运行、主动触发任务、跨端响应指令的“数字员工”。与Claude Code等传统大模型相比,OpenClaw的核心优势在于:持续在线、定时任务、主动推送、持久记忆、多端访问,真正实现“人只做创造性工作,系统包揽重复性劳动”。
184 3
|
7天前
|
安全 JavaScript Java
若依后台权限核心:Spring Security 认证授权详解
若依(RuoYi)框架整合 Spring Security 的具体实现方式,我会从核心原理、整合步骤、关键配置、实战示例四个维度,结合若依前后端分离版(Spring Boot + Vue)的特点,给出可直接落地的整合方案,帮你理解若依是如何基于 Spring Security 实现权限管控
77 4
|
2月前
|
人工智能 自然语言处理 Java
Spring AI Alibaba实战:从0到1构建企业级智能应用
本文介绍了基于SpringAI Alibaba框架开发AI原生应用的实战指南。文章首先分析了SpringAI Alibaba作为SpringAI本土化版本的核心优势,包括深度适配阿里云生态、中文语境优化等特性。随后详细讲解了开发环境的搭建过程,包括JDK17、SpringBoot3.2.2等技术栈的配置。通过三个实战案例展示了核心功能实现:基础文本生成、结合MyBatisPlus的智能问答系统、以及流式响应和函数调用等高级特性。
1377 6
|
3月前
|
Arthas 监控 Java
arthas 安装
本教程介绍Arthas的安装与基础使用。需提前安装JDK并确保服务器有Java应用运行。下载arthas-boot.jar,上传Java应用(如Arthas-demo.jar),启动后通过`java -jar arthas-boot.jar`接入,选择对应进程即可监控。支持多Java进程管理,为后续深入使用奠定基础。(239字)
|
1天前
|
XML Java 数据安全/隐私保护
深入 Spring IoC 容器底层:从原理到实战,一文讲透控制反转的核心逻辑
本文深入解析Spring IoC容器的实现机制,从核心架构、初始化流程到Bean生命周期。Spring IoC通过BeanFactory和ApplicationContext两个层次实现对象管理,采用控制反转和依赖注入降低组件耦合。详细介绍了Bean的实例化、属性填充、初始化和销毁四个阶段,以及核心组件BeanDefinition和BeanWrapper的作用。通过用户管理系统的实战案例,展示了@Service、@RestController等注解如何实现Bean注册和依赖注入。
34 2
|
1天前
|
XML Java 数据安全/隐私保护
彻底搞懂 Spring Boot 自动配置原理:从源码拆解到手写 Starter,零废话全干货
本文深入解析SpringBoot自动配置原理,基于SpringBoot 3.4.2版本详细拆解了自动配置的执行流程。主要内容包括:1)自动配置的本质是基于条件注解的动态JavaConfig配置类;2)核心执行流程通过AutoConfigurationImportSelector实现;3)SpringBoot 3.x采用新的自动配置注册方式;4)重点讲解了@Conditional系列条件注解的使用场景与常见坑点;5)通过开发自定义加密Starter实战演示完整实现过程。
47 2
|
1天前
|
人工智能 监控 安全
阿里云部署OpenClaw(Clawdbot)接入QVeris:重构量化交易逻辑,AI全自动炒股,告别人工盯盘!
在AI赋能金融分析的浪潮中,个人投资者面临的核心痛点日益凸显:人工盯盘耗时耗力、市场动态难以及时捕捉、专业分析工具门槛高成本高。而OpenClaw(原Clawdbot/Moltbot)凭借开源灵活的架构,成为打造专属金融AI助手的首选——通过接入A股实时数据,它能实现24小时市场监控、涨跌预警、潜力股推荐等核心功能,彻底解放人工盯盘的繁琐。
351 2
|
17天前
|
Python
抖音弹幕游戏开发之第11集:礼物触发功能·优雅草云桧·卓伊凡
《抖音弹幕游戏开发专栏》由优雅草云桂主讲、卓伊凡辅助,第11集详解礼物触发功能:解析giftName等核心字段,设计玫瑰摇摆、爱心跳跃、火箭旋转等规则,并用Python实现带次数限制的实时交互效果。
57 10
|
17天前
|
人工智能 开发工具 开发者
2026年,让.NET再次伟大
2026年,“让.NET再次伟大”倡议推动.NET SDK预装入Windows及主流Linux——以单文件(.cs)即运行为核心,统一工具链、降低开发门槛、赋能AI原生应用,重塑开发者体验与软件分发范式。(239字)

热门文章

最新文章