Spring 从零到生产级掌握
Spring 不能只背“IOC、AOP”。商业项目里真正要会的是:一个类为什么能变成 Bean,Bean 什么时候创建,依赖怎么注入,为什么有时拿到的是代理对象,事务为什么会失效,扩展点为什么能支撑 Spring Boot 自动配置、MyBatis Mapper、Feign Client 这些能力。
一句话建立主线:
Spring 是一个以 IOC 容器为核心、以扩展点为骨架、以 AOP 代理为增强手段的企业应用基础框架。
如果你想检查自己是否真的从零基础学懂到能面试、能落地、能排查,按 Spring 从零到精通验收清单 逐项验收。它把 BeanDefinition、Bean 生命周期、依赖注入、循环依赖、AOP、声明式事务、编程式事务、扩展点、商业场景和故障排查拆成可检查的问题。
学习目标
学完这一页,你要能做到:
- 解释 Spring 解决了什么问题,不只是会写
@Service、@Autowired。 - 解释 BeanDefinition、BeanFactory、ApplicationContext、单例池之间的关系。
- 解释一个 Bean 从扫描、注册、实例化、依赖注入、初始化、代理到销毁的完整过程。
- 解释字段注入、构造器注入、Setter 注入的差异和生产建议。
- 解释循环依赖为什么只解决部分场景,为什么构造器循环依赖解决不了。
- 解释 AOP 为什么依赖代理,为什么同类自调用失效。
- 解释声明式事务和编程式事务的原理、差异、失效原因。
- 解释 Spring 常见扩展点如何改变 Bean 定义、Bean 对象、配置导入和事件流转。
学习路线
flowchart TD
A["为什么需要 Spring"] --> B["BeanDefinition<br/>把类变成定义"]
B --> C["BeanFactory<br/>根据定义创建对象"]
C --> D["依赖注入<br/>把对象组装起来"]
D --> E["Bean 生命周期<br/>初始化和销毁"]
E --> F["BeanPostProcessor<br/>修改或包装 Bean"]
F --> G["AOP 代理<br/>事务、日志、权限"]
G --> H["扩展点<br/>Boot、MyBatis、Feign"]
H --> I["生产排查<br/>注入失败、代理失效、事务失效"]这张图不要横着做很宽,因为用户真正需要的是按学习顺序一步步理解。
第一步:为什么需要 Spring
没有 Spring 时,业务对象通常由业务代码自己创建:
public class OrderService {
private final InventoryService inventoryService = new InventoryService();
private final PayService payService = new PayService();
}这样写能跑,但项目一复杂就会出问题:
| 问题 | 具体表现 | 后果 |
|---|---|---|
| 对象创建分散 | 每个类都自己 new 依赖 | 替换实现困难,测试困难 |
| 依赖关系隐藏 | 看构造逻辑才能知道依赖什么 | 代码维护成本高 |
| 初始化不可控 | 连接池、线程池、客户端到处初始化 | 资源泄漏或重复创建 |
| 横切逻辑侵入 | 每个方法都写日志、事务、权限 | 业务代码变脏,重复代码多 |
Spring 的做法是:
- 业务类只声明“我需要什么”。
- 容器负责“创建对象、注入依赖、管理生命周期”。
- 横切能力通过代理在方法外层统一增强。
@Service
public class OrderService {
private final InventoryService inventoryService;
private final PayService payService;
public OrderService(InventoryService inventoryService, PayService payService) {
this.inventoryService = inventoryService;
this.payService = payService;
}
}为什么推荐构造器注入:依赖关系在构造方法中一眼可见,对象创建后依赖不可变,单元测试也可以直接传入假实现。
第二步:BeanDefinition 是什么
Spring 不是扫描到一个类就立刻创建对象。它会先把类解析成 BeanDefinition。
BeanDefinition 可以理解为“创建 Bean 的说明书”。
| 信息 | 例子 | 作用 |
|---|---|---|
| beanClass | OrderService.class | 后续根据它实例化对象 |
| scope | singleton、prototype | 决定单例还是多例 |
| constructor args | 构造参数 | 决定构造器注入 |
| property values | 属性依赖 | 决定 Setter 或字段注入 |
| init/destroy method | 初始化和销毁方法 | 管理生命周期 |
| lazyInit | 是否懒加载 | 决定启动时是否创建 |
| primary/qualifier | 候选优先级 | 多实现时选择哪个 Bean |
完整流程:
flowchart TD
A["扫描 @ComponentScan 路径"] --> B["发现 @Component/@Service"]
B --> C["读取注解和类信息"]
C --> D["生成 BeanDefinition"]
D --> E["注册到 BeanDefinitionMap"]
E --> F["后续按定义创建 Bean"]如果没有 BeanDefinition,Spring 就无法在创建对象前统一处理作用域、懒加载、条件注册、代理、属性依赖等信息。
第三步:BeanFactory 和 ApplicationContext
BeanFactory 是基础 IOC 容器,核心职责是保存 BeanDefinition、创建 Bean、获取 Bean。
ApplicationContext 是更完整的应用上下文,在 BeanFactory 之上增加:
| 能力 | 说明 |
|---|---|
| 国际化 | MessageSource |
| 事件机制 | ApplicationEventPublisher |
| 资源加载 | ResourceLoader |
| 环境配置 | Environment、Profile、PropertySource |
| 自动创建单例 | 启动时预实例化非懒加载单例 Bean |
面试里不要只答“ApplicationContext 是 BeanFactory 子接口”。更完整的回答是:
BeanFactory 是 Bean 容器的底层抽象,负责 Bean 的定义、创建和获取。ApplicationContext 是面向应用的上下文,在 BeanFactory 基础上增加事件、资源、环境、国际化等企业应用能力,并通常会在启动时创建非懒加载单例 Bean。
第四步:一个 Bean 的完整生命周期
Bean 生命周期是 Spring 原理的中心。很多问题都能放到这个流程里定位:注入失败、循环依赖、AOP 代理、事务失效、初始化顺序。
flowchart TD
A["BeanDefinition 已注册"] --> B["实例化<br/>调用构造器"]
B --> C["属性填充<br/>依赖注入"]
C --> D["Aware 回调<br/>拿容器资源"]
D --> E["BeanPostProcessor 前置"]
E --> F["初始化<br/>@PostConstruct/afterPropertiesSet"]
F --> G["BeanPostProcessor 后置"]
G --> H["可能生成代理对象"]
H --> I["放入单例池给业务使用"]
I --> J["容器关闭时销毁"]每一步做什么:
| 阶段 | 做什么 | 典型扩展 |
|---|---|---|
| BeanDefinition 注册 | 保存 Bean 的元信息 | ImportBeanDefinitionRegistrar |
| 实例化 | 调构造器创建原始对象 | InstantiationAwareBeanPostProcessor |
| 属性填充 | 注入依赖对象和值 | @Autowired、@Resource |
| Aware | 注入 Spring 容器相关对象 | ApplicationContextAware |
| 前置后处理 | 初始化前处理 Bean | BeanPostProcessor#postProcessBeforeInitialization |
| 初始化 | 执行业务初始化逻辑 | @PostConstruct、InitializingBean |
| 后置后处理 | 初始化后处理 Bean | AOP 在这里创建代理 |
| 销毁 | 释放资源 | @PreDestroy、DisposableBean |
注意:AOP 代理通常是在初始化后由 BeanPostProcessor 包装出来的。因此最终放入单例池的可能不是原始对象,而是代理对象。
第五步:依赖注入怎么选择
Spring 常见注入方式:
| 方式 | 写法 | 优点 | 缺点 | 建议 |
|---|---|---|---|---|
| 构造器注入 | 构造方法参数 | 依赖清晰、不可变、易测试 | 参数太多时暴露类职责过重 | 推荐 |
| Setter 注入 | setter 方法 | 可选依赖友好 | 对象可能处于半初始化状态 | 可选依赖使用 |
| 字段注入 | 字段上 @Autowired | 写法短 | 隐藏依赖、测试不便 | 不推荐生产核心代码 |
多实现时如何选择:
public interface SmsClient {
void send(String phone, String content);
}
@Service("aliyunSmsClient")
public class AliyunSmsClient implements SmsClient {
public void send(String phone, String content) {}
}
@Service("tencentSmsClient")
public class TencentSmsClient implements SmsClient {
public void send(String phone, String content) {}
}@Service
public class NoticeService {
private final SmsClient smsClient;
public NoticeService(@Qualifier("aliyunSmsClient") SmsClient smsClient) {
this.smsClient = smsClient;
}
}如果不指定,Spring 发现一个接口有多个候选 Bean,就可能报 NoUniqueBeanDefinitionException。
第六步:循环依赖为什么只解决部分场景
循环依赖例子:
@Service
public class AService {
@Autowired
private BService bService;
}
@Service
public class BService {
@Autowired
private AService aService;
}Spring 单例 Bean 有三级缓存思路:
| 缓存 | 保存什么 | 作用 |
|---|---|---|
| 一级缓存 | 完整单例 Bean | 正常获取 Bean |
| 二级缓存 | 早期 Bean 引用 | 暂时解决依赖 |
| 三级缓存 | ObjectFactory | 需要时生成早期引用,兼容 AOP |
简化流程:
flowchart TD
A["创建 A 原始对象"] --> B["A 放入三级缓存工厂"]
B --> C["A 注入 B,开始创建 B"]
C --> D["B 需要 A"]
D --> E["从三级缓存拿 A 的早期引用"]
E --> F["B 创建完成"]
F --> G["回到 A,注入完成"]
G --> H["A 初始化完成并放入一级缓存"]为什么构造器循环依赖解决不了:
public AService(BService bService) {}
public BService(AService aService) {}构造器注入要求“创建 A 之前必须先有 B,创建 B 之前必须先有 A”。对象连原始实例都没有,无法提前暴露引用。
生产建议:不要把循环依赖当能力用。出现循环依赖通常说明职责边界混乱,可以提取编排服务、领域事件、接口回调或中间状态表解耦。
第七步:AOP 为什么依赖代理
AOP 解决的是横切逻辑问题,比如事务、日志、权限、监控、审计。
没有 AOP:
public void pay() {
log.info("start");
beginTransaction();
try {
doPay();
commit();
} catch (Exception e) {
rollback();
throw e;
}
}业务代码被横切逻辑污染。
Spring AOP 的方式:
flowchart TD
A["调用方"] --> B["代理对象"]
B --> C["前置增强<br/>日志/事务开始"]
C --> D["目标方法"]
D --> E["后置增强<br/>提交/记录耗时"]
D --> F["异常增强<br/>回滚/告警"]JDK 动态代理和 CGLIB:
| 方式 | 原理 | 适合场景 | 限制 |
|---|---|---|---|
| JDK 动态代理 | 基于接口生成代理类 | 目标类实现接口 | 必须有接口 |
| CGLIB | 基于继承生成子类 | 没有接口的类 | final 类或方法不能增强 |
自调用为什么失效:
@Service
public class OrderService {
public void outer() {
inner(); // this.inner(),没有经过代理对象
}
@Transactional
public void inner() {}
}outer() 内部调用 inner() 本质是 this.inner(),绕过了代理对象,事务拦截器没有机会执行。
第八步:Spring 事务完整原理
声明式事务:
@Service
public class AssetService {
@Transactional
public void createAsset(Asset asset) {
assetMapper.insert(asset);
assetLogMapper.insert(asset.toLog());
}
}底层流程:
flowchart TD
A["调用代理方法"] --> B["TransactionInterceptor"]
B --> C["读取 @Transactional 属性"]
C --> D["获取连接并关闭自动提交"]
D --> E["执行目标方法"]
E --> F["正常返回"]
F --> G["提交事务"]
E --> H["抛出需回滚异常"]
H --> I["回滚事务"]
G --> J["释放连接"]
I --> J声明式事务和编程式事务:
| 类型 | 写法 | 原理 | 适用场景 |
|---|---|---|---|
| 声明式事务 | @Transactional | AOP 代理包裹方法 | 常规 Service 写操作 |
| 编程式事务 | TransactionTemplate | 代码主动控制边界 | 批处理分批提交、局部事务、异常要转返回值 |
编程式事务 Demo:
@Service
public class ImportService {
private final TransactionTemplate transactionTemplate;
private final AssetMapper assetMapper;
public ImportService(TransactionTemplate transactionTemplate, AssetMapper assetMapper) {
this.transactionTemplate = transactionTemplate;
this.assetMapper = assetMapper;
}
public void importOne(Asset asset) {
transactionTemplate.executeWithoutResult(status -> {
assetMapper.insert(asset);
if (asset.getCode() == null) {
status.setRollbackOnly();
}
});
}
}事务失效高频原因:
| 原因 | 为什么失效 | 怎么修 |
|---|---|---|
| 同类自调用 | 绕过代理对象 | 拆到另一个 Bean,或通过代理调用 |
| 方法不是 public | 默认代理无法按预期增强 | 事务方法用 public |
| 异常被 catch | 代理看到正常返回 | 重新抛出或 setRollbackOnly() |
| checked exception | 默认只回滚 RuntimeException/Error | 配置 rollbackFor = Exception.class |
| 新线程执行 | 事务上下文绑定当前线程 | 不要跨线程共享事务,改为消息/任务补偿 |
| 数据库不支持事务 | 比如 MyISAM | 使用 InnoDB |
第九步:核心扩展点怎么理解
Spring 强大不是因为内置所有能力,而是它提供了很多扩展点。
flowchart TD
A["BeanDefinition 阶段"] --> B["BeanDefinitionRegistryPostProcessor"]
A --> C["BeanFactoryPostProcessor"]
D["Bean 实例阶段"] --> E["BeanPostProcessor"]
D --> F["FactoryBean"]
G["配置导入阶段"] --> H["ImportSelector"]
G --> I["ImportBeanDefinitionRegistrar"]
J["运行期解耦"] --> K["ApplicationEvent"]常见扩展点:
| 扩展点 | 发生阶段 | 能做什么 | 商业项目例子 |
|---|---|---|---|
| BeanFactoryPostProcessor | Bean 实例化前 | 修改 BeanDefinition | 统一修改配置属性 |
| BeanDefinitionRegistryPostProcessor | BeanDefinition 注册阶段 | 新增 BeanDefinition | 扫描自定义 Mapper |
| BeanPostProcessor | Bean 初始化前后 | 包装 Bean、生成代理 | AOP、事务、异步、缓存 |
| FactoryBean | 创建复杂对象 | 暴露产品对象而不是工厂 | MyBatis Mapper 代理 |
| ImportSelector | 配置导入 | 按条件导入配置类 | Spring Boot 自动配置 |
| ImportBeanDefinitionRegistrar | 注册 BeanDefinition | 手动注册复杂 Bean | Feign Client、Mapper |
| ApplicationListener | 事件监听 | 解耦业务动作 | 订单完成后发通知 |
扩展点 Demo:事件解耦
public record AssetCreatedEvent(Long assetId) {}@Service
public class AssetService {
private final ApplicationEventPublisher publisher;
public AssetService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void createAsset(Long assetId) {
// 1. 写资产表
// 2. 发布事件,让通知、审计、搜索同步各自处理
publisher.publishEvent(new AssetCreatedEvent(assetId));
}
}@Component
public class AssetAuditListener {
@EventListener
public void onAssetCreated(AssetCreatedEvent event) {
System.out.println("record audit log: " + event.assetId());
}
}如果所有逻辑都写在 createAsset() 里,资产创建会越来越臃肿。事件可以让“主流程”和“后置动作”解耦。但也要注意:默认同步事件仍在当前线程执行,耗时任务应考虑异步事件或消息队列。
第十步:商业项目怎么落地
以医疗数据采集与资产平台为例:
| 业务场景 | Spring 能力 | 为什么这样用 |
|---|---|---|
| 多数据源采集 | IOC + 策略接口 | 不同数据库、接口、文件采集实现可替换 |
| 采集批次落库 | 声明式事务 | 批次状态和明细一致提交 |
| 失败补偿记录 | REQUIRES_NEW 或编程式事务 | 主事务回滚时仍保留失败日志 |
| 数据清洗规则 | Bean 集合注入 | 多个规则按顺序执行 |
| 审计日志 | AOP 或事件 | 不侵入业务主流程 |
| 搜索同步 | 事件 + MQ | 主库写成功后异步同步 ES |
| 自定义 starter | ImportSelector + 条件注解 | 公司统一日志、鉴权、监控组件 |
集合注入规则 Demo:
public interface CleanRule {
int order();
Asset clean(Asset asset);
}
@Service
public class CleanPipeline {
private final List<CleanRule> rules;
public CleanPipeline(List<CleanRule> rules) {
this.rules = rules.stream()
.sorted(Comparator.comparingInt(CleanRule::order))
.toList();
}
public Asset clean(Asset asset) {
Asset result = asset;
for (CleanRule rule : rules) {
result = rule.clean(result);
}
return result;
}
}这就是 Spring 在商业项目中的价值:业务代码面向接口和规则扩展,而不是到处写 if else 和 new。
生产排查总流程
注入失败怎么查
flowchart TD
A["启动报注入失败"] --> B["Bean 是否被扫描到"]
B --> C["包路径是否在 ComponentScan 下"]
C --> D["是否有多个候选 Bean"]
D --> E["是否需要 @Qualifier/@Primary"]
E --> F["是否条件注解不满足"]
F --> G["查看启动日志和 BeanDefinition"]代理或事务失效怎么查
flowchart TD
A["事务/切面没生效"] --> B["调用是否经过 Spring Bean"]
B --> C["是否同类自调用"]
C --> D["方法是否 public"]
D --> E["异常是否被 catch"]
E --> F["是否多线程切换"]
F --> G["数据库是否支持事务"]Bean 创建异常怎么查
| 现象 | 优先看什么 |
|---|---|
NoSuchBeanDefinitionException | Bean 没注册,扫描路径、条件注解、配置类 |
NoUniqueBeanDefinitionException | 多个候选 Bean,@Primary、@Qualifier |
BeanCurrentlyInCreationException | 循环依赖,尤其构造器循环依赖 |
BeanCreationException | 构造器、初始化方法、依赖 Bean 异常 |
| 事务没回滚 | 异常类型、自调用、catch、线程、数据库引擎 |
常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 所有地方字段注入 | 依赖隐藏,测试困难 | 核心业务用构造器注入 |
| 过度依赖循环依赖 | 设计耦合严重 | 拆服务、提接口、事件解耦 |
| 在构造器里做复杂逻辑 | 依赖未完全就绪 | 放到初始化方法或启动监听 |
| 事务方法 private | 代理无法增强 | 使用 public Service 方法 |
| catch 异常不抛 | 事务提交脏数据 | 重新抛出或标记回滚 |
| 事件里做耗时操作 | 主流程变慢 | 异步事件或 MQ |
| 扩展点乱用 | 启动链路难排查 | 只在框架集成和基础设施层使用 |
面试标准回答
Spring 的核心是什么
Spring 的核心是 IOC 和 AOP。IOC 负责对象创建、依赖注入和生命周期管理,让业务代码不再自己维护复杂依赖。AOP 基于代理对象增强方法调用,用来实现事务、日志、权限、监控等横切能力。它们背后依赖 BeanDefinition、BeanFactory、BeanPostProcessor、代理机制和事务拦截器等基础设施。
Bean 生命周期怎么说
Bean 通常经历 BeanDefinition 注册、实例化、依赖注入、Aware 回调、BeanPostProcessor 前置处理、初始化方法、BeanPostProcessor 后置处理、可能创建代理、放入单例池、容器关闭时销毁。AOP 代理一般就是后置处理器在初始化之后包装出来的。
Spring 事务为什么会失效
Spring 声明式事务基于 AOP 代理。事务失效通常是因为调用没有经过代理,比如同类自调用;或者方法不是 public;或者异常被 catch 后没有抛出;或者抛出 checked exception 但没配置 rollbackFor;或者事务代码跑到了新线程;也可能是数据库表本身不支持事务。
Spring 扩展点怎么回答
可以按阶段回答:BeanDefinition 阶段有 BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor;Bean 实例阶段有 BeanPostProcessor、FactoryBean;配置导入阶段有 ImportSelector、ImportBeanDefinitionRegistrar;运行期解耦有 ApplicationEvent 和 ApplicationListener。Spring Boot 自动配置、MyBatis Mapper、Feign Client 本质上都大量利用这些扩展点接入 Spring 容器。
关联知识点
本章小结
Spring 学习不要停在注解用法。你要把它看成一条链:类被扫描成 BeanDefinition,容器根据定义创建 Bean,依赖注入把对象组装起来,生命周期扩展点允许框架介入,AOP 代理提供事务和横切增强。能把这条链讲清楚,IOC、AOP、事务、循环依赖、自动配置和各种框架集成才会真正串起来。
