Spring IoC 容器:Bean 生命周期、依赖注入与条件装配
IoC 容器的形式化模型与依赖图构建、BeanDefinition 注册、循环依赖三级缓存、Bean 生命周期全流程、三种依赖注入方式、作用域与条件装配、自动配置机制;含 BeanPostProcessor 实战与 Bean 内存泄漏、启动慢优化案例。
知识点地图
- 知识类别:IoC 容器与依赖注入——Spring Framework 官方文档 Core Technologies 的 IoC container 一章(容器、Bean 概览、依赖、作用域、自定义 Bean 特性、注解驱动配置)。
- 解决什么问题:对象自己
new依赖会导致组件强耦合、无法替换实现、无法单测;IoC 容器把「对象怎么造、依赖从哪来」这件事收归集中管理。 - 什么时候用到:一切 Spring/Spring Boot 项目的地基;排查「Bean 没注入」「循环依赖启动失败」「@Value 注不进来」「单例被共享出脏数据」这类问题的原理层。
AOP(825 篇)与声明式事务(827 篇)已从本篇拆出独立成文;Spring MVC 请求处理流程与 RESTful 接口实战并入 Spring Boot 深度指南 第 6 章。本篇聚焦 IoC 容器本位。
前置知识
- Java 设计模式:工厂模式对应 IoC,代理模式对应 AOP;
- 建议先完成 013 面向对象编程、014 抽象类与接口、015 内部类、018 集合框架、029 注解与枚举、031 泛型详解、067 原生 JDBC。
学习目标
读完本文你将能够:
- 写出 IoC 容器的三元组模型,并解释 BeanDefinition 与依赖拓扑排序;
- 复述三级缓存解决循环依赖的流程,回答「为什么三级而非二级」;
- 说全 Bean 生命周期的实例化 → 属性填充 → Aware → 前后处理 → 初始化 → 销毁各阶段;
- 对比三种依赖注入方式并说明构造器注入为什么是推荐项;
- 用条件注解控制装配,并解释 Spring Boot 自动配置的加载机制。
预计 90 分钟,含 2 道动手任务。
0. 前置必读与阅读警告(先读这一节)
警告:这是全模块最不建议跳级阅读的文档之一。 如果还没有完成 Java SE 核心,@Autowired、BeanPostProcessor 对你就是天书。请按顺序先读完:
- 013 面向对象编程(多态与接口回调);
- 014 抽象类与接口、015 内部类(匿名内部类与代理的基础);
- 018 集合框架、029 注解与枚举、031 泛型详解(@Component/@Autowired 与 Repository 的语法前提);
- 067 原生 JDBC(先会底层数据访问)、071 设计模式(工厂模式对应 IoC,代理模式对应 AOP)。
推荐顺序:067 JDBC → 068-070 构建工具 → 071 设计模式 → 本篇。跳级阅读者请做好反复回查的准备。
1. 历史动机与背景
1.1 EJB 时代的痛点
2000 年代初,Java 企业级开发由 EJB(Enterprise JavaBeans)主导。EJB 2.x 的核心问题包括:
- 过度复杂:一个实体 Bean 需要编写 4 个类与 2 个部署描述符。
- 强耦合容器:业务代码必须继承 EJB 抽象类、实现容器接口,难以脱离容器测试。
- 部署繁琐:需要打包 EAR、配置 ejb-jar.xml、启动重量级应用服务器(WebLogic、WebSphere)。
- 性能开销大:远程调用、对象序列化、容器拦截器层层叠加。
Rod Johnson 在 2002 年出版的 Expert One-on-One J2EE Design and Development 一书中批判了 EJB 的复杂性,并提出了基于普通 Java 对象(POJO)的轻量级方案。这一思想催生了 Spring 框架。
1.2 Spring 的诞生
2003 年 6 月,Spring 框架 0.9 版本在 SourceForge 上发布。其核心理念是:
- POJO 编程模型:业务代码不依赖框架 API,保持可测试性。
- IoC 容器:组件依赖由容器注入,而非组件自己创建。
- AOP:将横切关注点(日志、事务、安全)与业务逻辑分离。
- 声明式编程:通过配置或注解描述”做什么”,由框架实现”怎么做”。
1.3 Spring 的演进
| 版本 | 发布年份 | 核心特性 |
|---|---|---|
| Spring 1.0 | 2004 | IoC 容器、AOP、XML 配置 |
| Spring 2.0 | 2006 | 自定义 XML 命名空间、@AspectJ 支持 |
| Spring 2.5 | 2007 | 注解驱动(@Autowired、@Component) |
| Spring 3.0 | 2009 | Java 配置(@Configuration)、REST 支持 |
| Spring 4.0 | 2013 | Java 8 支持、WebSocket、泛型注入 |
| Spring 5.0 | 2017 | 响应式编程(WebFlux)、JDK 9+ |
| Spring 6.0 | 2022 | JDK 17+ 基线、Jakarta EE 9+ 迁移 |
| Spring Boot 3.0 | 2022 | 原生镜像支持、Jakarta EE 迁移 |
1.4 Spring Boot 的革命
2014 年发布的 Spring Boot 解决了 Spring 的”配置地狱”问题:
- 约定优于配置:默认值合理,无需显式声明。
- Starter 依赖:
spring-boot-starter-web一行依赖引入完整 Web 栈。 - 内嵌容器:Tomcat/Jetty/Undertow 内嵌于 JAR,
java -jar直接启动。 - Actuator:内置生产级监控端点。
- 自动配置:基于 classpath 内容自动装配 Bean。
Spring Boot 让 Spring 应用的启动时间从分钟级降至秒级,部署从 WAR 改为可执行 JAR,推动了微服务与云原生的普及。
2. 形式化定义
2.1 IoC 容器的形式化模型
IoC 容器可形式化为三元组:
其中:
- :Bean 定义集合,
- :依赖关系图,,若 表示 依赖
- :Bean 工厂方法,
容器启动时构建 (有向图),通过拓扑排序确定创建顺序,对每个 调用 生成实例并注入依赖。
2.2 依赖注入的形式化
依赖注入是 IoC 的具体实现机制,形式化为:
注入方式有三种:
- 构造器注入:,依赖通过构造函数传入。
- Setter 注入:,依赖通过 setter 方法注入。
- 字段注入:,依赖通过反射直接赋值字段。
2.3 Bean 生命周期形式化
Bean 生命周期可形式化为状态机:
各状态语义:
- :未创建
- :实例化完成(构造器已调用)
- :属性填充完成(依赖已注入)
- :Aware 接口回调完成
- :BeanPostProcessor#postProcessBeforeInitialization 完成
- :初始化方法(afterPropertiesSet / @PostConstruct)完成
- :BeanPostProcessor#postProcessAfterInitialization 完成(AOP 代理在此生成)
- :Bean 就绪,可被使用
- :销毁完成
3. 理论推导
3.1 IoC 容器的依赖图构建
3.1.1 BeanDefinition 的注册
Spring 启动时扫描 classpath,对每个标注 @Component 的类创建 BeanDefinition 对象:
// BeanDefinition 的核心字段(简化版)
public class GenericBeanDefinition implements BeanDefinition {
private String beanClassName; // Bean 类全名
private String scope = SCOPE_SINGLETON; // 作用域
private boolean lazyInit = false; // 是否懒加载
private ConstructorArgumentValues constructorArgumentValues; // 构造参数
private MutablePropertyValues propertyValues; // 属性值
private String[] dependsOn; // 显式依赖
// ... 其他字段
}BeanDefinition 注册到 DefaultListableBeanFactory.beanDefinitionMap(ConcurrentHashMap)。
3.1.2 依赖图的拓扑排序
DefaultListableBeanFactory.preInstantiateSingletons() 按依赖顺序创建 Bean:
- 遍历所有非懒加载的 Bean 名称。
- 对每个 Bean 调用
getBean(name),触发创建。 getBean调用doGetBean,先递归创建依赖(getBean(dep))。- 依赖创建完成后再创建当前 Bean。
若依赖图中存在环,则触发循环依赖处理(见 3.2)。
3.1.3 创建过程的核心代码
// DefaultListableBeanFactory.doGetBean 简化逻辑
protected <T> T doGetBean(String name, Class<T> requiredType) {
// 1. 检查单例缓存
Object bean = getSingleton(name, false);
if (bean != null) {
return (T) bean;
}
// 2. 递归创建依赖
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
// 先创建依赖 Bean
getBean(dep);
// 注册依赖关系(用于循环依赖检测)
registerDependentBean(dep, beanName);
}
}
// 3. 创建 Bean 实例
if (mbd.isSingleton()) {
bean = createSingleton(beanName, mbd, args);
} else if (mbd.isPrototype()) {
bean = createPrototype(beanName, mbd, args);
}
return (T) bean;
}3.2 循环依赖与三级缓存
3.2.1 问题场景
@Service
public class A {
@Autowired private B b; // A 依赖 B
}
@Service
public class B {
@Autowired private A a; // B 依赖 A
}创建 A 时需要 B,创建 B 时又需要 A,形成死循环。
3.2.2 三级缓存结构
Spring 使用三级缓存解决单例 Bean 的循环依赖:
public class DefaultSingletonBeanRegistry {
// 一级缓存:完整的单例 Bean(已初始化完成)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期暴露的 Bean(已实例化,未完成属性填充)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:Bean 工厂(用于生成早期引用,支持 AOP)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}3.2.3 解决流程
- 创建 A,实例化后(属性未填充)将 A 的工厂放入三级缓存。
- A 开始填充属性 B,触发
getBean(B)。 - 创建 B,实例化后放入三级缓存。
- B 填充属性 A,调用
getSingleton(A):- 一级缓存查不到,进入二级缓存。
- 二级缓存查不到,进入三级缓存。
- 三级缓存找到 A 的工厂,调用工厂方法获取 A 的早期引用(可能是代理),放入二级缓存,从三级缓存移除。
- B 拿到 A 的早期引用,完成属性填充,初始化完成,放入一级缓存。
- A 继续填充属性 B,从一级缓存获取 B(已就绪),完成填充与初始化,放入一级缓存。
3.2.4 三级缓存的作用
- 一级缓存:存放完整 Bean,避免重复创建。
- 二级缓存:存放早期 Bean,避免循环依赖中重复创建。
- 三级缓存:存放 ObjectFactory,延迟 AOP 代理的生成时机。
为什么需要三级而非二级?当 Bean 需要 AOP 代理时,ObjectFactory 在被调用时才生成代理对象。若只有二级缓存,AOP 代理必须提前生成,违反”代理在初始化后生成”的设计原则。三级缓存使代理生成时机保持灵活。
3.2.5 循环依赖的局限
Spring 的三级缓存方案仅支持单例 Bean 的 setter 注入循环依赖,以下情况无法解决:
- 构造器注入循环依赖:构造器注入在实例化阶段就需要依赖,无法提前暴露早期引用。
- prototype 作用域循环依赖:prototype 每次创建新实例,无缓存。
- @Async 标注的 Bean 循环依赖:
@Async的代理在初始化后生成,与三级缓存机制冲突。
3.3 Bean 生命周期详解
3.3.1 完整生命周期
// Bean 生命周期的完整流程(伪代码)
public Object createBean(String beanName, RootBeanDefinition mbd) {
// 阶段 1:实例化前的前置处理(InstantiationAwareBeanPostProcessor)
Object wrappedBean = resolveBeforeInstantiation(beanName, mbd);
if (wrappedBean != null) {
return wrappedBean; // 短路,跳过后续流程
}
// 阶段 2:实例化(调用构造器)
BeanWrapper instance = createBeanInstance(beanName, mbd, args);
// 阶段 3:属性填充前处理(InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation)
// 阶段 4:属性值解析与填充(@Autowired、@Value 在此注入)
populateBean(beanName, mbd, instance);
// 阶段 5:初始化
wrappedBean = initializeBean(beanName, instance, mbd);
return wrappedBean;
}
private Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
// 阶段 5.1:Aware 接口回调
invokeAwareMethods(beanName, bean);
// - BeanNameAware.setBeanName
// - BeanClassLoaderAware.setBeanClassLoader
// - BeanFactoryAware.setBeanFactory
// - ApplicationContextAware.setApplicationContext(通过 ApplicationContextAwareProcessor)
// 阶段 5.2:BeanPostProcessor#postProcessBeforeInitialization
// - @PostConstruct 在此触发(CommonAnnotationBeanPostProcessor)
// - @Autowired 在此触发(部分场景)
wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
// 阶段 5.3:初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
// - InitializingBean.afterPropertiesSet()
// - 自定义 init-method
// 阶段 5.4:BeanPostProcessor#postProcessAfterInitialization
// - AOP 代理在此生成(AbstractAutoProxyCreator)
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}3.3.2 销毁流程
容器关闭时,对每个单例 Bean 执行销毁:
@PreDestroy注解方法(由InitDestroyAnnotationBeanPostProcessor处理)DisposableBean.destroy()接口方法- 自定义
destroy-method
3.4 Spring Boot 自动配置
3.4.1 @SpringBootApplication 的组成
@SpringBootConfiguration // 等同于 @Configuration,标记主配置类
@EnableAutoConfiguration // 开启自动配置
@ComponentScan // 扫描当前包及子包
public @interface SpringBootApplication {
// ...
}3.4.2 @EnableAutoConfiguration 的原理
@EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector.class) 加载自动配置类:
public class AutoConfigurationImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
// (Spring Boot 2.7+ 文件名)或
// META-INF/spring.factories(旧版)
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重、过滤
return configurations.toArray(new String[0]);
}
}每个自动配置类通过 @Conditional 系列注解决定是否生效:
@ConditionalOnClass:classpath 存在指定类时生效@ConditionalOnMissingBean:容器中不存在指定 Bean 时生效@ConditionalOnProperty:配置属性满足条件时生效@ConditionalOnWebApplication:Web 应用时生效
3.4.3 自动配置示例
// DataSourceAutoConfiguration 简化版
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class })
protected static class PooledDataSourceConfiguration {
}
}逻辑:当 classpath 存在 DataSource 类、容器中无 DataSource Bean 时,根据 classpath 中的连接池(HikariCP 优先)自动配置数据源。
4. 代码示例
4.1 IoC 容器基础:XML 配置与注解配置对比
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* IoC 容器基础示例
* 演示 XML 配置与注解配置两种方式
*/
public class IocContainerDemo {
public static void main(String[] args) {
// 方式一:XML 配置
ClassPathXmlApplicationContext xmlContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService1 = xmlContext.getBean(UserService.class);
userService1.saveUser("张三");
xmlContext.close();
// 方式二:注解配置(推荐)
AnnotationConfigApplicationContext annotationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService2 = annotationContext.getBean(UserService.class);
userService2.saveUser("李四");
annotationContext.close();
}
}
/**
* Java 配置类
* 替代传统的 XML 配置文件
*/
@Configuration
@ComponentScan("com.example")
public class AppConfig {
/**
* 声明一个 Bean,等价于 <bean id="dateFormatter" class="java.text.SimpleDateFormat"/>
*/
@Bean
public SimpleDateFormat dateFormatter() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}
/**
* 用户服务类
* 使用 @Service 标记为 Service 层组件
*/
@Service
public class UserService {
private final UserRepository userRepository;
private final SimpleDateFormat dateFormatter;
/**
* 构造器注入(推荐方式)
* 优点:不可变、非空、易测试
*/
@Autowired
public UserService(UserRepository userRepository, SimpleDateFormat dateFormatter) {
this.userRepository = userRepository;
this.dateFormatter = dateFormatter;
}
public void saveUser(String name) {
String timestamp = dateFormatter.format(new Date());
System.out.printf("[%s] 保存用户: %s%n", timestamp, name);
userRepository.insert(name);
}
}
@Repository
public class UserRepository {
public void insert(String name) {
System.out.println("数据库插入: " + name);
}
}4.2 三种依赖注入方式对比
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.Resource;
import javax.inject.Inject;
/**
* 三种依赖注入方式对比
* 演示构造器注入、Setter 注入、字段注入
*/
@Service
public class InjectionDemo {
// ========== 字段注入(不推荐)==========
// 优点:简洁
// 缺点:无法 final、隐藏依赖、难以测试、循环依赖检测失效
@Autowired
@Qualifier("primaryDataSource")
private DataSource fieldDataSource;
// ========== Setter 注入(可选依赖时使用)==========
private DataSource setterDataSource;
@Autowired(required = false)
public void setSetterDataSource(DataSource setterDataSource) {
this.setterDataSource = setterDataSource;
}
// ========== 构造器注入(推荐)==========
private final DataSource constructorDataSource;
private final UserRepository userRepository;
/**
* 构造器注入
* Spring 4.3+ 单构造器可省略 @Autowired
*/
public InjectionDemo(DataSource constructorDataSource, UserRepository userRepository) {
this.constructorDataSource = constructorDataSource;
this.userRepository = userRepository;
}
}
/**
* @Resource(JSR-250)与 @Autowired(Spring)对比
*/
@Service
public class ResourceVsAutowiredDemo {
// @Autowired 默认按类型注入,配合 @Qualifier 按名称
@Autowired
@Qualifier("mysqlDataSource")
private DataSource dataSource1;
// @Resource 默认按名称注入,找不到再按类型
@Resource(name = "mysqlDataSource")
private DataSource dataSource2;
// @Inject(JSR-330)与 @Autowired 类似,按类型注入
@Inject
private DataSource dataSource3;
}4.3 Bean 生命周期监听
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContextAware;
import org.springframework.beans.factory.BeanFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* Bean 生命周期演示
* 实现各种 Aware 接口与初始化回调,观察执行顺序
*/
public class LifecycleBean implements BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean {
private String beanName;
private String state;
public LifecycleBean() {
System.out.println("1. 构造器调用 - 实例化");
this.state = "created";
}
/**
* Setter 方法用于属性注入
*/
public void setState(String state) {
System.out.println("2. 属性填充 - setState");
this.state = state;
}
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware.setBeanName: " + name);
this.beanName = name;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4. BeanFactoryAware.setBeanFactory");
}
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
System.out.println("5. ApplicationContextAware.setApplicationContext");
}
@PostConstruct
public void postConstruct() {
System.out.println("6. @PostConstruct - 初始化前");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("7. InitializingBean.afterPropertiesSet - 初始化");
}
public void customInitMethod() {
System.out.println("8. customInitMethod - 自定义初始化方法");
}
@PreDestroy
public void preDestroy() {
System.out.println("9. @PreDestroy - 销毁前");
}
public void customDestroyMethod() {
System.out.println("10. customDestroyMethod - 自定义销毁方法");
}
}
/**
* BeanPostProcessor 实现
* 可在所有 Bean 的初始化前后插入逻辑
*/
@Component
public class LifecycleBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LifecycleBean) {
System.out.println("BPP Before - " + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LifecycleBean) {
System.out.println("BPP After - " + beanName + "(AOP 代理在此生成)");
}
return bean;
}
}4.4 自定义 Spring Boot Starter
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 自定义 Starter 的自动配置类
* 示例:一个短信服务 Starter
*/
@Configuration
@ConditionalOnClass(SmsService.class)
@EnableConfigurationProperties(SmsProperties.class)
@ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true", matchIfMissing = true)
public class SmsAutoConfiguration {
/**
* 注册 SmsService Bean
* 当容器中不存在该 Bean 时才创建
*/
@Bean
@ConditionalOnMissingBean
public SmsService smsService(SmsProperties properties) {
SmsService service = new SmsService();
service.setAccessKeyId(properties.getAccessKeyId());
service.setAccessKeySecret(properties.getAccessKeySecret());
service.setSignName(properties.getSignName());
service.setEndpoint(properties.getEndpoint());
return service;
}
}
/**
* 配置属性类
* 绑定 application.yml 中 sms.* 的配置
*/
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
private boolean enabled = true;
private String accessKeyId;
private String accessKeySecret;
private String signName;
private String endpoint = "https://sms.aliyuncs.com";
// getter / setter 省略
}
/**
* 短信服务
*/
public class SmsService {
private String accessKeyId;
private String accessKeySecret;
private String signName;
private String endpoint;
public void send(String phone, String templateCode, String params) {
System.out.printf("发送短信到 %s: 模板=%s, 参数=%s%n", phone, templateCode, params);
// 实际调用阿里云/腾讯云短信 API
}
// getter / setter 省略
}在 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中注册:
com.example.sms.SmsAutoConfiguration4.5 自定义 BeanPostProcessor 实现日志埋点
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 自定义 BeanPostProcessor
* 为所有 Service 层 Bean 自动添加方法调用日志
*/
@Component
public class ServiceLoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 仅对 @Service 注解的 Bean 生效
if (bean.getClass().isAnnotationPresent(Service.class)
|| bean.getClass().getName().contains("Service")) {
return createLoggingProxy(bean);
}
return bean;
}
/**
* 创建日志代理
*/
@SuppressWarnings("unchecked")
private <T> T createLoggingProxy(T target) {
Class<?>[] interfaces = target.getClass().getInterfaces();
if (interfaces.length == 0) {
// 无接口,返回原对象(实际项目可用 CGLIB)
return target;
}
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
interfaces,
(proxy, method, args) -> {
long start = System.currentTimeMillis();
String methodName = target.getClass().getSimpleName() + "." + method.getName();
try {
System.out.printf("[Service-Call] %s args=%s%n", methodName, Arrays.toString(args));
Object result = method.invoke(target, args);
long elapsed = System.currentTimeMillis() - start;
System.out.printf("[Service-Done] %s %dms%n", methodName, elapsed);
return result;
} catch (Exception e) {
long elapsed = System.currentTimeMillis() - start;
System.err.printf("[Service-Error] %s %dms %s%n", methodName, elapsed, e.getMessage());
throw e;
}
}
);
}
}5. 对比分析
5.1 IoC 容器实现对比
| 特性 | BeanFactory | ApplicationContext |
|---|---|---|
| 加载时机 | 懒加载(getBean 时创建) | 预加载(启动时创建所有非懒加载 Bean) |
| 国际化 | 不支持 | 支持(MessageSource) |
| 事件机制 | 不支持 | 支持(ApplicationEvent) |
| 资源访问 | 不支持 | 支持(ResourceLoader) |
| AOP 集成 | 需手动配置 | 自动集成 |
| 注解支持 | 需手动添加 BeanPostProcessor | 自动开启 |
| 性能 | 启动快 | 启动稍慢 |
| 适用场景 | 资源受限环境 | 企业级应用(默认选择) |
5.2 依赖注入方式对比
| 注入方式 | 可变性 | 非空保证 | 循环依赖 | 测试性 | 推荐度 |
|---|---|---|---|---|---|
| 构造器注入 | final(不可变) | 编译期保证 | 不支持(启动失败) | 优 | 推荐 |
| Setter 注入 | 可变 | 运行时可能空 | 支持 | 良 | 可选依赖时使用 |
| 字段注入 | 可变 | 运行时可能空 | 支持 | 差 | 不推荐 |
6. 常见陷阱与反模式
6.1 反模式:字段注入导致测试困难
问题:
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 字段注入
public User getUser(Long id) {
return userRepository.findById(id);
}
}危害:
userRepository无法声明为final,可变性隐患。- 单元测试时无法直接赋值,必须依赖反射或 Spring 容器。
- 隐藏了类的真实依赖,看类签名无法知道依赖。
正确做法:
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}生产事故案例:某团队使用字段注入,单元测试覆盖率达 80% 但生产频繁出 bug。原因是测试用 @MockBean 替换依赖,掩盖了真实依赖关系;重构时漏改一个依赖,测试全过但生产 NPE。改为构造器注入后,编译期即可发现缺失依赖。
6.2 反模式:循环依赖
问题:
@Service
public class A {
private final B b;
public A(B b) { this.b = b; } // 构造器注入
}
@Service
public class B {
private final A a;
public B(A a) { this.a = a; } // 构造器注入
}现象:启动失败,BeanCurrentlyInCreationException。
原因:构造器注入的循环依赖无法通过三级缓存解决,因为实例化阶段就需要依赖。
修复方案:
- 重构消除循环:重新设计职责划分,将公共依赖抽取到第三个类。
- 改用 Setter 注入(不推荐,治标不治本):
java
@Service public class A { private B b; @Autowired public void setB(B b) { this.b = b; } } - 使用 @Lazy:
java
@Service public class A { private final B b; public A(@Lazy B b) { this.b = b; } // 注入代理,延迟初始化 }
6.3 反模式:Bean 作用域误用
问题:
@Component
public class UserController {
private User currentUser; // 有状态字段
public void handle(HttpServletRequest request) {
this.currentUser = parseUser(request); // 修改状态
process();
}
private void process() {
System.out.println(currentUser.getName());
}
}原因:@Component 默认 singleton,所有请求共享同一实例。并发请求时 currentUser 会被覆盖,产生数据错乱。
修复:
- 无状态 Bean(无字段或字段只读)使用
singleton。 - 有状态 Bean 使用
prototype或改为方法内局部变量。
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UserController {
private User currentUser;
// ...
}7. 工程实践
7.1 生产级 Spring Boot 配置
# application.yml 推荐配置
spring:
application:
name: order-service
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev} # 通过环境变量切换
# 数据源
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: ${DB_USER:root}
password: ${DB_PASSWORD:}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
# JPA
jpa:
hibernate:
ddl-auto: validate # 生产环境禁用 update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
jdbc:
batch_size: 50
order_inserts: true
order_updates: true
# 日志
logging:
level:
root: INFO
com.example: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
file:
name: logs/${spring.application.name}.log
max-size: 100MB
max-history: 30
# Actuator 监控
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: when-authorized
metrics:
tags:
application: ${spring.application.name}
# 优雅停机
server:
shutdown: graceful
spring.lifecycle:
timeout-per-shutdown-phase: 30s7.2 性能优化实践
- Bean 懒加载:非启动必需的 Bean 标注
@Lazy,加快启动速度。 - 排除自动配置:在
@SpringBootApplication(exclude = {...})排除未使用的自动配置。 - 连接池调优:根据业务负载调整 HikariCP 参数,监控
hikaricp.connections.active。 - JPA 批处理:
spring.jpa.properties.hibernate.jdbc.batch_size=50+rewriteBatchedStatements=true。 - 异步处理:耗时操作用
@Async+ 线程池异步执行。 - 缓存:热点数据用
@Cacheable配合 Redis 缓存。
/**
* 异步任务配置
*/
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
/**
* 缓存配置
*/
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}7.3 监控与可观测性
/**
* 自定义 Micrometer 指标
*/
@Component
public class OrderMetrics {
private final Counter orderCreatedCounter;
private final Timer orderProcessingTimer;
private final Gauge orderPendingGauge;
public OrderMetrics(MeterRegistry registry, OrderRepository orderRepository) {
this.orderCreatedCounter = Counter.builder("orders.created")
.description("创建的订单总数")
.tag("type", "all")
.register(registry);
this.orderProcessingTimer = Timer.builder("orders.processing.time")
.description("订单处理耗时")
.register(registry);
this.orderPendingGauge = Gauge.builder("orders.pending",
() -> orderRepository.countByStatus("PENDING"))
.description("待处理订单数")
.register(registry);
}
public void recordOrderCreated() {
orderCreatedCounter.increment();
}
public Timer.Sample startTimer() {
return Timer.start();
}
public void stopTimer(Timer.Sample sample) {
sample.stop(orderProcessingTimer);
}
}7.4 优雅停机
/**
* 优雅停机钩子
* 在容器关闭前完成进行中的请求
*/
@Component
public class GracefulShutdownHook implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("应用关闭中,等待进行中请求完成...");
// Spring Boot 的 server.shutdown=graceful 已处理请求层
// 这里处理业务层的清理:关闭线程池、刷新缓冲区等
}
}8. 案例研究
8.1 案例:Bean 内存泄漏导致 OOM
场景:某电商系统上线 3 个月后频繁 OOM,重启后恢复正常。
排查过程:
- 通过
jmap -histo:live <pid>发现ConcurrentHashMap$Node实例数达 800 万。 - 通过
jmap -dump:format=b,file=heap.hprof <pid>导出堆 dump。 - MAT 分析发现
DefaultListableBeanFactory.singletonObjects持有大量 Bean。 - 进一步发现某个
@Component("userCache")的 Bean 是prototype作用域,但被其他 singleton Bean 持有引用,导致每次请求创建的 userCache 实例无法回收。
根因:
@Component
@Scope("prototype") // 每次注入创建新实例
public class UserCache {
private Map<Long, User> cache = new HashMap<>(); // 缓存持续增长
}
@Service
public class UserService {
@Autowired
private UserCache userCache; // 注入一次后持有,prototype 失效
public User getUser(Long id) {
return userCache.cache.computeIfAbsent(id, this::loadFromDb);
}
}修复:
- 使用
ObjectFactory或Provider注入 prototype Bean。 - 或改用
singleton+ 手动管理缓存生命周期。
@Service
public class UserService {
@Autowired
private ObjectFactory<UserCache> userCacheProvider; // 每次获取新实例
public User getUser(Long id) {
UserCache cache = userCacheProvider.getObject();
return cache.get(id);
}
}8.2 案例:Spring Boot 启动慢优化
场景:某微服务启动耗时 90 秒,影响弹性伸缩。
优化过程:
- 开启启动日志:
debug=true或logging.level.org.springframework=DEBUG。 - 分析瓶颈:发现
BeanPostProcessor数量达 200+,每个 Bean 都经过全部处理器。 - 懒加载:非必需 Bean 标注
@Lazy,启动时间降至 50 秒。 - 排除自动配置:
@SpringBootApplication(exclude = {LiquibaseAutoConfiguration.class, ...}),降至 40 秒。 - GraalVM Native Image:使用 Spring Boot 3 + Native Image,启动时间降至 0.1 秒(牺牲 JIT 优化)。
最终配置:
@SpringBootApplication(exclude = {
LiquibaseAutoConfiguration.class,
FlywayAutoConfiguration.class,
WebSocketServletAutoConfiguration.class
})
@EnableLazyInitialization // Spring Boot 2.2+ 全局懒加载
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}9. 习题
9.1 基础题
题目 1:Spring IoC 容器的核心作用是什么?BeanFactory 与 ApplicationContext 有何区别?
参考答案要点:
- IoC 容器负责对象的创建、依赖注入、生命周期管理。
BeanFactory是基础容器,懒加载;ApplicationContext扩展容器,预加载、支持事件、国际化、AOP。- 生产环境推荐
ApplicationContext。
题目 2:解释 Bean 的五种作用域。
参考答案要点:
- singleton:单例(默认),全容器共享一个实例。
- prototype:原型,每次获取创建新实例。
- request:每个 HTTP 请求一个实例(Web 上下文)。
- session:每个 HTTP Session 一个实例。
- application:每个 ServletContext 一个实例。
题目 3:@Autowired 与 @Resource 的区别?
参考答案要点:
@Autowired:Spring 注解,默认按类型注入,配合@Qualifier按名称。@Resource:JSR-250 标准注解,默认按名称注入,找不到再按类型。@Autowired可用于构造器、方法、字段;@Resource仅字段与 setter。
9.2 进阶题
题目 4:详细解释 Spring 如何解决单例 Bean 的循环依赖,为什么需要三级缓存而非二级?
参考答案要点:
- 三级缓存:
singletonObjects(完整 Bean)、earlySingletonObjects(早期 Bean)、singletonFactories(ObjectFactory)。 - 解决流程:A 创建中暴露工厂到三级缓存,B 创建时从三级缓存获取 A 的早期引用,B 完成后 A 继续填充。
- 三级缓存的作用:延迟 AOP 代理的生成时机。ObjectFactory 被调用时才生成代理,使代理生成时机保持灵活。
- 若只有二级缓存,AOP 代理必须提前生成,违反”代理在初始化后生成”的设计。
题目 5:分析 Spring Boot 自动配置的加载机制,说明如何排查”自动配置未生效”的问题。
参考答案要点:
- 加载机制:
@EnableAutoConfiguration→AutoConfigurationImportSelector→ 读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。 - 每个 AutoConfiguration 类通过
@Conditional决定是否生效。 - 排查步骤:
- 启动时加
--debug查看Conditions Evaluation Report。 - 检查
@ConditionalOnClass是否满足(依赖是否引入)。 - 检查
@ConditionalOnProperty是否满足(配置项是否存在且值正确)。 - 检查
@ConditionalOnMissingBean是否被用户自定义 Bean 覆盖。 - 检查自动配置类是否在 imports 文件中注册。
- 启动时加
10. 参考资料与延伸阅读
10.1 官方文档
- Spring Framework Reference(Core Technologies,IoC 容器章节): https://docs.spring.io/spring-framework/reference/core/beans.html
- Spring Boot Reference: https://docs.spring.io/spring-boot/docs/current/reference/
- Spring Guides: https://spring.io/guides
- Spring Initializr: https://start.spring.io/
10.2 经典教材
- Craig Walls, Spring in Action, Sixth Edition(Spring 实战,最权威入门)
- Iuliana Cosmina 等, Pro Spring 6(Spring 深度剖析)
- Craig MacKenzie 等, Spring Boot in Action(Spring Boot 实战)
- Grzegorz Piwowarek, Spring Boot: Up and Running(Spring Boot 快速上手)
10.3 源码与进阶
- Spring Framework 源码: https://github.com/spring-projects/spring-framework
- Spring Boot 源码: https://github.com/spring-projects/spring-boot
- Spring 源码深度解析(郝佳,国内最详细的源码剖析)
- Spring 技术内幕(计文柯,深入 Spring 设计思想)
10.4 相关主题
- Spring AOP:BeanPostProcessor 阶段生成的代理如何织入切面
- Spring 声明式事务:事务代理与事务管理器
- Java 与数据库连接:Spring 事务管理与 JPA 集成
- SpringBoot 进阶:Spring Boot 高级特性与生产实践
- SpringBoot 安全:Spring Security 集成与认证授权
11. 动手实践
任务一:观察完整生命周期(热身)
把 4.3 节的 LifecycleBean 与 LifecycleBeanPostProcessor 落到一个 Spring Boot 工程(或纯 AnnotationConfigApplicationContext 工程)里跑一遍,按打印顺序默写全部 10 个阶段,再对照 2.3 节状态机核对。
提示:注册组件用 @ComponentScan 指到 bean 所在包;观察 @PostConstruct 与 afterPropertiesSet 的先后。
任务二:制造并修复一次循环依赖(实战)
先按 6.2 节写两个构造器互相注入的类,确认启动抛 BeanCurrentlyInCreationException;再依次尝试三种修复(重构抽取公共依赖、setter 注入、@Lazy),记录每种修复后的启动结果与注入进来的对象形态。
提示:@Lazy 注入的其实是代理——可以在修复后打印 b.getClass() 观察类名是否为 CGLIB 生成的 $$SpringCGLIB$$ 形态。
任务三:条件装配开关
给 4.4 节的短信 Starter 加一个测试配置:application.yml 里 sms.enabled=false 时容器中不存在 SmsService Bean。用 ApplicationContext.containsBean("smsService") 验证开关效果,再用 --debug 启动查看 Conditions Evaluation Report 里该自动配置为何未生效。
提示:注意 @ConditionalOnProperty 的 matchIfMissing 参数对「配置缺失」场景的影响。
12. 语法速查:组件注册与依赖注入
基本写法:@Component 通用组件
@Component [("<bean名称>")]
// 注册为 Spring Bean
@Component
public class UserService { }基本写法:@Service 业务层
@Service [("<bean名称>")]
// 标记业务逻辑层组件
@Service
public class UserService { }基本写法:@Repository 持久层
@Repository [("<bean名称>")]
// 标记数据访问层组件
@Repository
public class UserRepository { }基本写法:@Controller 控制层
@Controller [("<bean名称>")]
// 标记 MVC 控制器
@Controller
public class HomeController { }基本写法:@RestController REST 控制器
@RestController
// 等同于 @Controller + @ResponseBody
@RestController
public class ApiController { }基本写法:@Configuration 配置类
@Configuration
// 声明配置类
@Configuration
public class AppConfig { }基本写法:@Bean 声明 Bean
@Bean [("<名称>")]
// 在配置类中声明 Bean
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}基本写法:@Autowired 按类型注入
@Autowired
// 按类型自动注入
@Autowired
private UserRepository userRepository;基本写法:@Autowired 构造方法注入
@Autowired (构造方法上方)
// 推荐的构造方法注入
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}基本写法:@Qualifier 按名称注入
@Qualifier("<bean名称>")
// 指定注入的 Bean 名称
@Autowired
@Qualifier("primaryDataSource")
private DataSource dataSource;基本写法:@Resource 按名称注入
@Resource(name = "<bean名称>")
// JSR-250 标准注解
@Resource(name = "userRepository")
private UserRepository userRepository;基本写法:@Value 注入配置值
@Value("${<属性键>}")
// 注入配置文件中的值
@Value("${app.name}")
private String appName;基本写法:@Scope 单例
@Scope("singleton")
// 单例作用域(默认)
@Scope("singleton")
@Component
public class SingletonService { }基本写法:@Scope 原型
@Scope("prototype")
// 每次注入都创建新实例
@Scope("prototype")
@Component
public class PrototypeService { }基本写法:@PostConstruct 初始化
@PostConstruct
// Bean 初始化完成后执行
@PostConstruct
public void init() {
System.out.println("Initialized");
}基本写法:@PreDestroy 销毁前
@PreDestroy
// Bean 销毁前执行
@PreDestroy
public void cleanup() {
System.out.println("Cleanup");
}基本写法:@Conditional 条件注册
@Conditional(<条件类>.class)
// 满足条件才注册 Bean
@Bean
@Conditional(OnLinuxCondition.class)
public DataSource linuxDataSource() { }基本写法:@Profile 环境配置
@Profile("<环境>")
// 仅在指定环境生效
@Bean
@Profile("dev")
public DataSource devDataSource() { }基本写法:@ConditionalOnProperty 属性条件
@ConditionalOnProperty(name = "<属性>", havingValue = "<值>")
// 配置属性满足条件时注册
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public CacheService cacheService() { }参考与致谢
- Spring Framework 官方文档:Core Technologies / IoC container(https://docs.spring.io/spring-framework/reference/core/beans.html ),容器与 Bean 生命周期的官方语义以此为准(Apache 2.0 许可项目文档,本文仅参照其概念表述,代码为原创示例);
- 原模块 Spring 基础篇拆分说明:AOP 内容已迁至 825-SpringAOP,事务内容已迁至 827-SpringDeclarativeTransactions,Spring MVC 请求处理流程与 RESTful 接口实战已迁至 860-SpringBootDeepDive 第 6 章。