Skip to content

Spring 核心 ​

Java 中级 · Spring 生态

Spring 的两大基石是 IoC(控制反转) 和 AOP(面向切面)。很多人会用 @Autowired、@Transactional,却说不清 Bean 是怎么创建和装配的、代理是何时生成的。本篇把容器启动、Bean 生命周期、AOP 代理、事务传播讲透——这是后面「Spring 源码剖析」的必经之路。

一、IoC 与 DI:控制反转与依赖注入 ​

IoC:对象的创建与依赖装配权,从「你 new」反转给「容器创建并注入」。DI(依赖注入)是 IoC 的实现手段。

java
// 传统:自己控制依赖
public class OrderService {
    private UserDao userDao = new UserDaoImpl();  // 强耦合,无法替换/测试
}

// IoC:容器注入依赖
@Service
public class OrderService {
    @Autowired
    private UserDao userDao;   // 由容器装配,面向接口,可替换、可 mock
}

1.1 三种注入方式 ​

方式写法评价
构造器注入通过构造方法参数推荐:不可变、非空、便于测试、暴露循环依赖
Setter 注入setXxx()适合可选依赖
字段注入@Autowired 标字段方便但不推荐:难以单测、掩盖循环依赖、违反单一职责
java
@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把通知织入目标
java
@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 最常见的失效场景

  1. 同类内部方法调用:this.methodB() 不走代理,@Transactional/@Async 失效。
  2. private / final 方法:无法被代理。
  3. 自己 new 的对象:不在容器里。 解决方案:注入自身、AopContext.currentProxy()、或把方法拆到另一个 Bean。

四、事务管理 ​

4.1 声明式事务 ​

java
@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 事务失效的经典原因 ​

text
1. 同类内部调用(不走代理)
2. 方法非 public(CGLIB 代理无法增强)
3. 异常被 catch 未抛出 / 抛的是受检异常未配 rollbackFor
4. 类未被 Spring 管理(自己 new)
5. 数据库引擎不支持事务(如 MyISAM)
6. 多数据源未正确配置事务管理器

五、Bean 作用域 ​

作用域说明
singleton(默认)容器内单例
prototype每次获取新实例
request / sessionWeb 请求/会话级

单例注入了 prototype 会怎样

单例 Bean 只创建一次,注入的 prototype Bean 也只是创建时那一个,不会每次刷新。要用 @Lookup 或 ObjectFactory 才能拿到新的。

六、最佳实践 ​

  • 构造器注入 + final 字段,拒绝字段注入。
  • @Transactional 加 rollbackFor = Exception.class,明确回滚范围。
  • 别在同类内部调用带事务/异步注解的方法,会失效。
  • AOP 切点尽量精确(按注解或包名),避免切到无关方法导致性能损耗。
  • 单例 Bean 保持无状态,共享可变状态要加锁或用 ThreadLocal。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 画出 Bean 的完整生命周期,指出 AOP 代理是在哪一步生成的。
  2. JDK 动态代理和 CGLIB 的区别是什么?Spring 如何选择?
  3. 列举至少 4 种 @Transactional 失效的场景,并给出修复思路。
  4. REQUIRED 和 REQUIRES_NEW 的区别是什么?什么场景用 REQUIRES_NEW?

本节小结

Spring 核心 = IoC(容器创建+装配 Bean)+ AOP(动态代理织入横切逻辑)。记住 Bean 生命周期里 BeanPostProcessor 是 AOP 生效的地方;代理有 JDK(接口)/ CGLIB(子类) 两种;事务默认只回滚 RuntimeException,同类内部调用、非 public、异常被吞是失效三大元凶。