CC 咖啡猫的工作空间 Coding Space

Spring 深入原理

Spring 的核心是控制反转(IoC)容器面向切面编程(AOP)。理解这两个核心,才能理解为什么 Spring 能管理 Bean 的生命周期,为什么 @Transactional 能生效,为什么 Spring 可以做到无侵入式增强。


1. 控制反转(IoC)容器

1.1 从"new"到"容器管理"

// 传统写法:自己 new 对象
public class OrderService {
    private UserDao userDao = new UserDao();  // 主动创建
}

// Spring 写法:把控制权交给容器
public class OrderService {
    @Autowired
    private UserDao userDao;  // 被动注入,容器负责创建
}

控制反转(IoC):不是你的代码创建对象,而是你把对象的管理权"反转"给 Spring 容器

1.2 Spring 容器启动过程

启动容器
    ↓
加载配置文件 / 扫描注解
    ↓
解析 Bean 定义(@ComponentScan、@Bean、BeanDefinition)
    ↓
实例化 Bean(反射调用构造方法)
    ↓
属性填充(@Autowired 注入依赖)
    ↓
初始化(@PostConstruct、InitializingBean)
    ↓
放入容器(单例池)
    ↓
使用
    ↓
销毁(@PreDestroy、DisposableBean)

1.3 Bean 作用域(Scope)

作用域 说明 使用场景
singleton 整个容器只有一个实例(默认) 无状态 Bean
prototype 每次获取都创建新实例 有状态 Bean
request 每次 HTTP 请求创建一个 Web 请求相关
session 每次 HTTP 会话创建一个 用户会话
application 整个应用生命周期一个 ApplicationContext 级别
websocket 每个 WebSocket 会话一个 WebSocket
@Service
@Scope("prototype")  // 每次注入都创建新实例
public class MyService {
    // ...
}

1.4 FactoryBean 工厂 Bean

不是直接创建 Bean,而是通过工厂方法获取:

@Component
public class MyFactory implements FactoryBean<MyObject> {

    @Override
    public MyObject getObject() {
        // 可以在此处做复杂初始化
        return new MyObject();
    }

    @Override
    public Class<?> getObjectType() {
        return MyObject.class;
    }

    @Override
    public boolean isSingleton() {
        return true;  // true=容器只创建一个,false=每次创建新实例
    }
}

FactoryBean 的特殊之处:注册到容器的是工厂,实际拿到的是 getObject() 返回的对象。

与 BeanFactory 的区别

  • BeanFactory:容器接口,负责创建和管理 Bean
  • FactoryBean:用于自定义对象创建逻辑的工厂接口

2. Spring AOP 核心原理

2.1 静态代理 vs 动态代理

静态代理:手动写代理类

// 接口
interface UserService {
    void addUser();
}

// 真实对象
class UserServiceImpl implements UserService {
    public void addUser() {
        System.out.println("插入用户");
    }
}

// 静态代理:手动写代理类
class UserServiceProxy implements UserService {
    private UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    public void addUser() {
        System.out.println("=== 开始事务 ===");  // 前置增强
        target.addUser();                        // 真实调用
        System.out.println("=== 提交事务 ===");  // 后置增强
    }
}

缺点:每个接口都要写一个代理类,代码膨胀。

动态代理:运行时生成代理类

// JDK 动态代理:基于接口
class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("=== 开始 ===");
        Object result = method.invoke(target, args);  // 调用真实方法
        System.out.println("=== 结束 ===");
        return result;
    }
}

// 使用
UserService proxy = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class},
    new MyInvocationHandler(new UserServiceImpl())
);
proxy.addUser();

JDK 动态代理要求被代理的类必须实现接口

CGLIB 动态代理:基于继承,可以代理没有接口的类

// CGLIB:通过继承生成子类,覆盖父类方法
class UserServiceImpl$$CGLIB$$0 extends UserServiceImpl {
    // 重写 addUser(),插入增强逻辑
}
// 优势:不需要接口,类继承即可
// 劣势:无法代理 final、private 方法(不能被重写)

2.2 Spring AOP 的代理选择

Spring AOP 决策树:

被代理的类有接口吗?
    │
    ├── 是 → JDK 动态代理(优先)
    │         ↓
    │    代理类实现相同接口,
    │    调用InvocationHandler
    │
    └── 否 → CGLIB 动态代理
              ↓
           生成子类,
           覆盖目标方法
// 强制使用 CGLIB
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)  // true=CGLIB,false=JDK
public class AppConfig {}

2.3 Spring AOP 的织入时机

织入方式 说明 性能
编译时织入(CTW) 编译时直接修改 class 最快,需特殊编译器
类加载时织入(LTW) 加载 class 时用 agent 修改 启动慢,运行时无开销
运行时织入 Spring AOP:运行时生成代理对象 默认,运行时需额外计算

Spring AOP 用的就是运行时织入:通过代理对象增强,方法调用时先走代理逻辑。

2.4 @Transactional 原理

@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        // 1. 查询商品
        Product p = productDao.findById(productId);
        // 2. 扣库存
        productDao.updateStock(productId, p.getStock() - 1);
        // 3. 创建订单
        orderDao.insert(order);
    }
}

Spring 的处理:

方法被 @Transactional 标注
    ↓
Spring 在创建这个 Bean 时,发现有 @Transactional
    ↓
生成一个代理对象(Proxy),覆盖原方法
    ↓
调用 createOrder() 时:
    ↓
    1. 检查是否已有事务
    2. 没有则开启新事务(关闭自动提交、设置隔离级别)
    3. 执行目标方法(target.createOrder())
    4. 提交事务 / 回滚事务
    ↓
真实对象是 OrderServiceImpl,但拿到的其实是代理对象

为什么 self 调用事务不生效?

@Service
public class OrderService {

    @Transactional
    public void createOrder() {      // 这个方法的事务不生效!
        this.validate();              // this 是真实对象,不是代理对象
        this.saveOrder();
    }

    @Transactional
    public void validate() { ... }   // 事务在这里开了也没用

    @Transactional
    public void saveOrder() { ... }
}

this.xxx() 调用的是真实对象的方法,不是代理对象的方法。解决方案:用 AopContext.currentProxy() 获取代理对象,或注入自身。

2.5 Spring 事务详解

2.5.1 事务传播行为(Propagation)

事务传播行为定义了多个事务方法相互调用时,事务如何在这些方法间传播。

传播行为 说明 使用场景
REQUIRED (默认) 如果当前存在事务,则加入该事务;如果不存在,则创建新事务 大多数业务场景
SUPPORTS 如果当前存在事务,则加入该事务;如果不存在,则以非事务方式执行 查询操作
MANDATORY 如果当前存在事务,则加入该事务;如果不存在,则抛出异常 必须在事务中执行的操作
REQUIRES_NEW 创建新事务,如果当前存在事务,则挂起当前事务 独立的事务操作(如日志记录)
NOT_SUPPORTED 以非事务方式执行,如果当前存在事务,则挂起当前事务 不需要事务的操作
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常 绝对不能在事务中执行的操作
NESTED 如果当前存在事务,则在嵌套事务内执行;否则创建新事务 需要部分回滚的场景
@Service
public class OrderService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder() {
        // 主业务逻辑
        orderDao.insert(order);
        // 调用日志服务,即使日志失败也不影响主业务
        logService.logOrderCreated(orderId); 
    }
}

@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOrderCreated(String orderId) {
        // 独立事务,失败不会回滚主业务
        logDao.insert(log);
    }
}

2.5.2 事务隔离级别(Isolation)

事务隔离级别定义了事务之间的可见性规则,解决并发问题。

隔离级别 脏读 不可重复读 幻读 说明
READ_UNCOMMITTED 最低隔离级别,性能最好
READ_COMMITTED 大多数数据库默认级别
REPEATABLE_READ MySQL InnoDB 默认级别
SERIALIZABLE 最高隔离级别,性能最差
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 30)
public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) {
    // 转账逻辑
}

2.5.3 事务回滚规则

默认情况下,Spring 只对 RuntimeExceptionError 进行回滚,对 checked Exception 不回滚。

// 自定义回滚规则
@Transactional(rollbackFor = Exception.class)  // 对所有异常回滚
public void businessMethod() throws IOException {
    // 业务逻辑
}

@Transactional(noRollbackFor = BusinessException.class)  // BusinessException 不回滚
public void anotherMethod() {
    // 业务逻辑
}

2.5.4 事务超时和只读

@Transactional(timeout = 30, readOnly = true)
public List<User> findAllUsers() {
    // 只读查询,设置超时时间30秒
    return userDao.findAll();
}
  • timeout:事务超时时间(秒),超时后自动回滚
  • readOnly:只读事务,可以进行优化(如禁用脏检查)

2.5.5 编程式事务管理

除了声明式事务,Spring 还支持编程式事务管理:

@Service
public class OrderService {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    public void createOrderProgrammatically() {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            // 业务逻辑
            orderDao.insert(order);
            productDao.updateStock(productId, newStock);
            
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

2.5.6 事务失效的常见场景

  1. 自调用问题:同一个类中的方法调用,如前所述
  2. 非 public 方法@Transactional 只能用于 public 方法
  3. 异常被捕获:异常在方法内部被捕获且未重新抛出
  4. 错误的异常类型:抛出 checked exception 但未配置 rollbackFor
  5. 代理对象问题:直接通过 new 创建对象而不是从容器获取
@Service
public class OrderService {
    
    // 错误:异常被捕获但未重新抛出
    @Transactional
    public void badExample() {
        try {
            orderDao.insert(order);
            throw new RuntimeException("error");
        } catch (Exception e) {
            // 异常被捕获,事务不会回滚!
            log.error("error", e);
        }
    }
    
    // 正确:重新抛出异常
    @Transactional
    public void goodExample() {
        try {
            orderDao.insert(order);
            throw new RuntimeException("error");
        } catch (Exception e) {
            log.error("error", e);
            throw e; // 重新抛出,确保事务回滚
        }
    }
}

2.5.7 分布式事务

对于跨多个数据源的场景,Spring 提供了多种分布式事务解决方案:

  1. JTA(Java Transaction API):传统的分布式事务标准
  2. Seata:阿里巴巴开源的分布式事务框架
  3. 消息队列 + 本地事务表:最终一致性方案
  4. TCC(Try-Confirm-Cancel):补偿型事务模式
// 使用 Seata 的 @GlobalTransactional
@GlobalTransactional
public void createOrderWithInventory() {
    // 订单服务
    orderService.createOrder(order);
    // 库存服务
    inventoryService.decreaseStock(productId, quantity);
}

在微服务架构中,通常优先考虑最终一致性而非强一致性,以提高系统可用性和性能。


3. Spring 事件机制

3.1 观察者模式在 Spring 中的应用

事件发布者(Publisher)
    ↓  发布事件
ApplicationEvent
    ↓
事件监听器(Listener)→ 接收并处理
    ↓
ApplicationListener 接口实现

3.2 实现自定义事件

// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final String orderId;
    public OrderCreatedEvent(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }
    public String getOrderId() { return orderId; }
}

// 2. 发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void createOrder() {
        // 创建订单逻辑...
        publisher.publishEvent(new OrderCreatedEvent(this, orderId));
    }
}

// 3. 监听事件
@Component
public class OrderCreatedListener implements ApplicationListener<OrderCreatedEvent> {

    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        String orderId = event.getOrderId();
        // 发短信、发邮件、发送MQ消息等
    }
}

3.3 Spring 内置事件

事件 触发时机
ContextRefreshedEvent 容器刷新完成
ContextStartedEvent 容器启动
ContextStoppedEvent 容器停止
ContextClosedEvent 容器关闭
RequestHandledEvent HTTP 请求处理完成(需开启 RequestContextListener

4. Spring 循环依赖

4.1 什么是循环依赖

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;  // B 依赖 A,形成循环
}

A 需要注入 B,B 需要注入 A,Spring 如何解决?

4.2 三级缓存解决循环依赖

// Spring 的三级缓存
public class DefaultSingletonBeanRegistry {

    // 一级缓存:成品 Bean(已经完全初始化)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 二级缓存:半成品 Bean(属性填充完成,但未初始化)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    // 三级缓存:Bean 工厂(用于创建半成品 Bean)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

    // 获取 Bean
    protected Object getSingleton(String beanName, boolean allowEarlyCreation) {
        // 1. 先从一级缓存拿(成品)
        Object singleton = singletonObjects.get(beanName);
        if (singleton == null) {
            // 2. 一级缓存没有,从三级缓存拿工厂创建半成品
            ObjectFactory<?> factory = singletonFactories.get(beanName);
            if (factory != null) {
                Object earlySingleton = factory.getObject();
                earlySingletonObjects.put(beanName, earlySingleton);
                singletonFactories.remove(beanName);
            }
        }
        return singleton;
    }
}

解决流程

创建 A:
  一级缓存没有 A → 开始创建 A
    ↓
  A 的构造方法执行前,提前暴露到三级缓存
    ↓
  填充属性 b → 调用 getBean("b")
    ↓
  创建 B:
    一级缓存没有 B → 开始创建 B
      ↓
    填充属性 a → 调用 getBean("a")
      ↓
    一级缓存没有 A,但三级缓存有 A 的工厂
      → 获取 A 的早期引用(半成品)
      → 存入二级缓存,删除三级缓存
      → B 完成创建,存入一级缓存
    ↓
  返回到 A 的创建流程
  → 从一级缓存拿到完整的 B
  → A 完成创建,存入一级缓存

为什么需要三级缓存而不是二级?

二级缓存无法处理 AOP:代理对象在 BeanPostProcessor 中创建,三级缓存的工厂方法在 populateBean 之前被调用,此时代理还没生成。三级缓存保证了代理对象的正确创建。

4.3 构造器循环依赖无法解决

A(A a) { this.a = a; }
B(B b) { this.b = b; }

构造器注入时,在构造方法执行完之前对象都无法创建,无法提前暴露早期引用,所以 Spring 无法解决构造器循环依赖。

解决方案:改用 setter 注入或 @Lazy 延迟注入。


5. Spring 启动与自动配置

5.1 @Enable 模块化

@EnableTransactionManagement  // 开启事务管理
@EnableScheduling             // 开启定时任务
@EnableCaching                // 开启缓存

原理:@EnableXxx 引入了一个 @Import(XxxConfiguration.class),该 Configuration 往容器注册了相关的 Bean 后置处理器。

5.2 @Conditional 条件注册

@Bean
@ConditionalOnClass(RedisTemplate.class)  // 存在 RedisTemplate 类才注册
public RedisTemplate<String, Object> redisTemplate() {
    // ...
}

Spring Boot 的自动配置大量使用 @Conditional,根据类路径判断是否启用某个功能。


6. Spring 常见问题

6.1 Bean 之间的依赖关系如何决定创建顺序

Spring 使用**有向无环图(DAG)**拓扑排序决定顺序:

A ← B    即 A 依赖 B
↓
C        即 C 依赖 A 和 B

如果图中存在环,Spring 启动失败并报循环依赖错误。

6.2 BeanPostProcessor 的执行时机

// Bean 后置处理器接口
public interface BeanPostProcessor {
    // 实例化前调用
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }
    // 实例化后调用
    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

执行顺序:

实例化(构造方法)→ 属性填充 → 
  ↓
BeanPostProcessor.postProcessBeforeInitialization
  ↓
@PostConstruct → InitializingBean.afterPropertiesSet → init-method
  ↓
BeanPostProcessor.postProcessAfterInitialization
  ↓
放入单例池

常见的后置处理器:AutowiredAnnotationBeanPostProcessor(处理 @Autowired)、RequiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor(处理 @Resource 等)。

6.3 Spring 事务的隔离级别

@Transactional(isolation = Isolation.READ_COMMITTED)
public void transfer(String from, String to, BigDecimal amount) {
    // ...
}
隔离级别 说明 脏读 不可重复读 幻读
READ_UNCOMMITTED 未提交读
READ_COMMITTED 已提交读(默认)
REPEATABLE_READ 可重复读
SERIALIZABLE 串行化

MySQL 默认 REPEATABLE_READ,Spring 默认 Isolation.DEFAULT(使用数据库的默认隔离级别)。