Spring 核心
Java 中级 · Spring 生态
Spring 的两大基石是 IoC(控制反转) 和 AOP(面向切面)。很多人会用 @Autowired、@Transactional,却说不清 Bean 是怎么创建和装配的、代理是何时生成的。本篇把容器启动、Bean 生命周期、AOP 代理、事务传播讲透——这是后面「Spring 源码剖析」的必经之路。
一、IoC 与 DI:控制反转与依赖注入
IoC:对象的创建与依赖装配权,从「你 new」反转给「容器创建并注入」。DI(依赖注入)是 IoC 的实现手段。
// 传统:自己控制依赖
public class OrderService {
private UserDao userDao = new UserDaoImpl(); // 强耦合,无法替换/测试
}
// IoC:容器注入依赖
@Service
public class OrderService {
@Autowired
private UserDao userDao; // 由容器装配,面向接口,可替换、可 mock
}1.1 三种注入方式
| 方式 | 写法 | 评价 |
|---|---|---|
| 构造器注入 | 通过构造方法参数 | 推荐:不可变、非空、便于测试、暴露循环依赖 |
| Setter 注入 | setXxx() | 适合可选依赖 |
| 字段注入 | @Autowired 标字段 | 方便但不推荐:难以单测、掩盖循环依赖、违反单一职责 |
@Service
public class OrderService {
private final UserDao userDao;
// 单构造器时可省略 @Autowired(Spring 4.3+)
public OrderService(UserDao userDao) {
this.userDao = userDao;
}
}二、容器启动与 Bean 生命周期
2.1 容器层次
2.2 Bean 生命周期(简化版)
记住两个扩展点
BeanPostProcessor 是 Spring 的灵魂:AOP、@Autowired、@Async 等几乎都靠它「介入」。postProcessAfterInitialization 返回的可能是代理对象,这就是 AOP 生效的地方。
三、AOP:动态代理
AOP 把横切关注点(日志、事务、权限、限流)从业务代码中抽离。
| 概念 | 含义 |
|---|---|
| 切面 Aspect | 横切逻辑的模块 |
| 连接点 JoinPoint | 可被增强的点(方法) |
| 切点 Pointcut | 匹配连接点的表达式 |
| 通知 Advice | 增强动作(前置/后置/环绕/异常/最终) |
| 织入 Weaving | 把通知织入目标 |
@Aspect
@Component
public class LogAspect {
@Around("@annotation(com.demo.Log)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed(); // 执行目标方法
} finally {
System.out.println(pjp.getSignature() + " 耗时 " + (System.currentTimeMillis() - start) + "ms");
}
}
}3.1 两种代理
| 对比 | JDK 动态代理 | CGLIB |
|---|---|---|
| 要求 | 目标必须实现接口 | 目标类可被继承(非 final) |
| 原理 | 生成实现接口的代理类 | 生成目标类的子类 |
| 性能 | 生成快,调用稍慢 | 生成稍慢,调用快 |
| 默认 | 有接口时优先 | 无接口时用 |
AOP 最常见的失效场景
- 同类内部方法调用:
this.methodB()不走代理,@Transactional/@Async失效。 - private / final 方法:无法被代理。
- 自己 new 的对象:不在容器里。 解决方案:注入自身、
AopContext.currentProxy()、或把方法拆到另一个 Bean。
四、事务管理
4.1 声明式事务
@Service
public class OrderService {
@Autowired private OrderMapper orderMapper;
@Autowired private StockService stockService;
@Transactional(rollbackFor = Exception.class) // 默认只回滚 RuntimeException
public void createOrder(Order order) {
orderMapper.insert(order);
stockService.deduct(order.getProductId(), order.getQty()); // 同事务
}
}默认只回滚 RuntimeException
@Transactional 默认遇 RuntimeException/Error 回滚,遇受检异常不回滚。要回滚受检异常必须写 rollbackFor = Exception.class。另外异常被自己 catch 吞掉也不会回滚。
4.2 事务传播行为(7 种)
| 传播行为 | 说明 |
|---|---|
REQUIRED(默认) | 有事务就加入,没有就新建 |
REQUIRES_NEW | 总是新建事务,挂起外层 |
NESTED | 嵌套事务(savepoint),外层回滚会带上内层 |
SUPPORTS | 有就加入,没有就以非事务运行 |
NOT_SUPPORTED | 以非事务运行,挂起外层 |
MANDATORY | 必须在事务中,否则抛异常 |
NEVER | 必须无事务,否则抛异常 |
4.3 事务失效的经典原因
1. 同类内部调用(不走代理)
2. 方法非 public(CGLIB 代理无法增强)
3. 异常被 catch 未抛出 / 抛的是受检异常未配 rollbackFor
4. 类未被 Spring 管理(自己 new)
5. 数据库引擎不支持事务(如 MyISAM)
6. 多数据源未正确配置事务管理器五、Bean 作用域
| 作用域 | 说明 |
|---|---|
singleton(默认) | 容器内单例 |
prototype | 每次获取新实例 |
request / session | Web 请求/会话级 |
单例注入了 prototype 会怎样
单例 Bean 只创建一次,注入的 prototype Bean 也只是创建时那一个,不会每次刷新。要用 @Lookup 或 ObjectFactory 才能拿到新的。
六、最佳实践
- 构造器注入 + final 字段,拒绝字段注入。
@Transactional加rollbackFor = Exception.class,明确回滚范围。- 别在同类内部调用带事务/异步注解的方法,会失效。
- AOP 切点尽量精确(按注解或包名),避免切到无关方法导致性能损耗。
- 单例 Bean 保持无状态,共享可变状态要加锁或用 ThreadLocal。
📚 官方文档与参考资料
✏️ 练习与自测
- 画出 Bean 的完整生命周期,指出 AOP 代理是在哪一步生成的。
- JDK 动态代理和 CGLIB 的区别是什么?Spring 如何选择?
- 列举至少 4 种
@Transactional失效的场景,并给出修复思路。 REQUIRED和REQUIRES_NEW的区别是什么?什么场景用REQUIRES_NEW?
本节小结
Spring 核心 = IoC(容器创建+装配 Bean)+ AOP(动态代理织入横切逻辑)。记住 Bean 生命周期里 BeanPostProcessor 是 AOP 生效的地方;代理有 JDK(接口)/ CGLIB(子类) 两种;事务默认只回滚 RuntimeException,同类内部调用、非 public、异常被吞是失效三大元凶。