Spring 高级特性深度解析:从 IoC 容器到 AOP 代理,彻底搞懂 Spring 的底层逻辑

 

开场:为什么你写的 Spring 代码有时会”不听话”?

你有没有遇到过这些情况:

  • @Autowired 注入的对象竟然是 null
  • @Transactional 加了但没生效,数据还是回滚了
  • 两个 Bean 互相 @Autowired,启动时就报循环依赖错误
  • AOP 切面配了,但某些方法死活不进切面

这些问题,表面上是”用法不对”,本质上是对 Spring 底层机制理解不够。

Spring 就像一座冰山——你能看到 @Service@Autowired@Transactional 这些露在水面上的注解,但水面下还藏着 Bean 生命周期、代理对象创建、事务传播、事件广播……这些机制,才是 Spring 的”真骨血”。

这篇文章,我带你把 Spring 高级特性的底层逻辑全部过一遍。看完之后,上面那些”不听话”的问题,你都能自己找到答案。


一、先搭框架:Spring 容器的启动全景图

在深入每个特性之前,先看一眼全局。

Spring 容器启动,本质上是一个对象制造工厂的初始化过程,分 5 步走完:

容器启动 5 阶段

  阶段一:容器创建
    → new AnnotationConfigApplicationContext(config)

  阶段二:配置扫描
    → @ComponentScan 扫描 @Component / @Service / @Controller

  阶段三:Bean 注册
    → 把每个类注册成 BeanDefinition(名字、作用域、依赖关系)

  阶段四:Bean 实例化 + 初始化
    → 实例化 → 属性填充 → 钩子方法 → AOP 代理 → 放入容器

  阶段五:容器就绪
    → 所有 Bean 可用,ContextRefreshedEvent 事件发出

💡 小提示:Spring Boot 的自动装配,本质上也是在这个框架里运作的,只不过 BeanDefinition 是通过 META-INF/spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 自动注册进来的。


二、Bean 生命周期:Spring 到底对你写的类做了什么?

2.1 生命周期全流程

每个 Spring Bean 从”一个普通 Java 类”到”容器中的可用对象”,要经历 12 个步骤:

Bean 生命周期 12 步

  ① 实例化
    → new UserServiceImpl()

  ② 属性填充
    → @Autowired / @Value 注进去

  ③ BeanNameAware 回调
    → setBeanName("userServiceImpl")

  ④ BeanFactoryAware 回调
    → setBeanFactory(beanFactory)

  ⑤ ApplicationContextAware 回调
    → setApplicationContext(context)

  ⑥ BeanPostProcessor.postProcessBeforeInitialization()
    → 扩展点:初始化前处理(如 @PostConstruct)

  ⑦ InitializingBean.afterPropertiesSet()
    → 做你想做的初始化

  ⑧ 自定义 init-method
    → @Bean(initMethod = "init")

  ⑨ BeanPostProcessor.postProcessAfterInitialization()
    → 扩展点:初始化后处理(AOP 代理在这里生成)

  ⑩ Bean 使用中……

  ⑪ DisposableBean.destroy()
    → 做你想做的清理

  ⑫ 自定义 destroy-method
    → @Bean(destroyMethod = "cleanup")

⚠️ 注意:这里的 ⑥ 和 ⑨ 是 BeanPostProcessor 的两个方法,Spring 大量利用这两个扩展点来做功能,比如注解驱动(@Autowired 用的是 AutowiredAnnotationBeanPostProcessor)、AOP 代理(AnnotationAwareAspectJAutoProxyCreator)。

2.2 @PostConstruct 和 init-method 有什么区别?

很多人分不清这两个,这里直接说清楚:

特性 @PostConstruct init-method(XML) initMethod(@Bean)
来源 Java 注解( jakarta.annotation) Spring XML Spring @Bean
执行时机 在⑥之后⑦之前 在⑦之后⑧ 同左
能否接收参数
推荐场景 Spring 项目 遗留 XML 项目 Spring Boot

实战代码

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    // ✅ 推荐:@PostConstruct,Spring 标准方式
    @PostConstruct
    public void init() {
        // 此时所有 @Autowired 都已完成,可以安全使用
        log.info("UserService 初始化,userDao = {}", userDao);
    }

    // ✅ 也行:@Bean 指定 initMethod
    // @Bean(initMethod = "init")
}

2.3 BeanPostProcessor:Spring 的”插件系统”

BeanPostProcessor 是 Spring 提供给开发者的最强扩展点。它让你可以在 Bean 初始化前后”插一脚”,做自己想做的事。

接口定义

/**
 * Spring 的后置处理器接口
 *
 * 这个接口是 Spring 框架最重要的扩展点之一。
 * Spring 内部大量功能都基于它实现:
 *   - @Autowired / @Value    → AutowiredAnnotationBeanPostProcessor
 *   - @Transactional         → InstantiationAwareBeanPostProcessorAdapter
 *   - @Async                 → AsyncAnnotationBeanPostProcessor
 *   - @Required              → RequiredAnnotationBeanPostProcessor
 *
 * 工作原理:
 *   每个 Bean 实例化/初始化时,Spring 会遍历所有注册的
 *   BeanPostProcessor,依次调用这两个方法。
 */
public interface BeanPostProcessor {

    /**
     * 初始化前回调
     *
     * @param bean     刚刚实例化好的原始对象(还没做属性填充)
     * @param beanName Bean 的名字
     * @return 通常返回原对象,也可以返回包装后的对象
     */
    Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException;

    /**
     * 初始化后回调
     *
     * @param bean     完成初始化后的对象
     * @param beanName Bean 的名字
     * @return 通常返回原对象,也可以返回代理对象(AOP 代理就在这里生成)
     */
    Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException;
}

自定义实现示例

/**
 * 自定义后置处理器:打印所有 Bean 的初始化日志
 *
 * 实战场景:
 *   - 打印每个 Bean 初始化耗时,定位启动慢的问题
 *   - 对特定 Bean 做特殊处理(比如替换实现类)
 *   - 校验某些属性是否注入成功
 */
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        log.debug("▶ Bean 初始化前: {}", beanName);
        return bean; // 不做修改,原样返回
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        log.debug("◼ Bean 初始化完成: {} → {}", beanName, bean.getClass().getName());
        return bean;
    }
}

三、循环依赖:Spring 是怎么解决”鸡生蛋、蛋生鸡”问题的?

3.1 什么是循环依赖?

循环依赖,就是两个(或多个)Bean 互相依赖:

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

@Service
public class B {
    @Autowired
    private A a;  // B 依赖 A
}

启动 Spring 时,容器会尝试创建 A → 发现 A 需要 B → 尝试创建 B → 发现 B 需要 A → 死循环 → 报错。

3.2 三级缓存:Spring 的解决之道

Spring 不是简单地拒绝循环依赖,而是用三级缓存来解决这个问题:

缓存 名字 存什么 干什么
一级 singletonObjects 完全成品的 Bean 所有线程从这里拿 Bean
二级 earlySingletonObjects 提前暴露的半成品 Bean 解决循环依赖时用
三级 singletonFactories ObjectFactory 工厂 延迟创建代理对象

一句话理解:工厂生产代理 → 半成品应急 → 完全体交付

解决流程(以 A → B → A 为例)

步骤 发生了什么 缓存状态变化
1 尝试创建 A,一级没有 → 调用 createBean 三级:放入 A 的工厂
2 填充 A 的属性,发现依赖 B → 尝试 getBean(“B”)
3 尝试创建 B,一级没有 → 调用 createBean 三级:放入 B 的工厂
4 填充 B 的属性,发现依赖 A → 尝试 getBean(“A”)
5 关键:A 不在一二级,但三级有 A 的工厂 三级取出 A 工厂 → 二级
三级:删除 A
二级:有 A(半成品)
5 续 把半成品 A 注入给 B B 拿到 A
6 B 创建完成 一级:放入 B(完全体)
二三:删除 B
7 把 B 注入给 A → A 创建完成 一级:放入 A(完全体)
二三:删除 A

完成! 核心在于步骤 5 —— B 等 A 时,虽然 A 还没”做好”,但三级缓存提前暴露了一个”半成品 A”,让 B 能继续往下走。循环就此打破。

3.3 为什么需要三级?二级不行吗?

这是面试高频题,我直接说结论:

为什么不能只用二级缓存?

二级缓存只能解决”普通 Bean”的循环依赖,但如果 A 需要被 AOP 增强,问题就来了:

  • B 需要注入 A 时,A 必须是代理对象(才能加事务)
  • 但此时 A 还在创建中,Spring 还没决定要不要给它加代理
  • 二级缓存只能存”对象”,不能”延迟决定要不要代理”

三级缓存做了什么?

  • 三级存的不是对象,是”创建代理的工厂”(ObjectFactory
  • 每次从三级取,调用工厂 → 拿到的是代理对象
  • 代理对象放进二级,B 拿到的是带 AOP 功能的 A
缓存 存什么 干什么
一级 完全体 Bean 交付给所有线程
二级 半成品 Bean 循环依赖时应急
三级 代理工厂 延迟决定要不要代理

3.4 构造器注入的循环依赖,为什么 Spring 无法解决?

@Service
public class A {
    public A(B b) {  // ❌ 构造器注入
        this.b = b;
    }
}

原因:构造器注入发生在 new 那一刻,Bean 还没创建出来,根本进不了三级缓存。Spring 对构造器循环依赖直接拒绝,没有解法——这是设计层面的取舍。

工程建议:优先使用 @Autowired(setter 注入)或 @Lazy 注解打破循环。

@Service
public class A {
    // 方案1:@Lazy 延迟加载,注入一个代理对象
    @Autowired
    @Lazy
    private B b;

    // 方案2:setter 注入(推荐),允许循环时容器灵活处理
    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

四、AOP 动态代理:Spring 是怎么”偷偷”给你加代码的?

4.1 为什么需要 AOP?

业务代码里,总有一些”横切关注点”(Cross-Cutting Concerns)——日志、事务、安全、缓存——它们分散在各处,但逻辑都一样:

❌ 没有 AOP 时:每个方法都要手动写

public class UserServiceImpl {
    public void addUser(User user) {
        log.info("开始添加用户: {}", user.getName());
        try {
            // ==== 业务代码 ====
            userDao.insert(user);
            // ==== 业务代码结束 ====
            log.info("添加用户成功");
        } catch (Exception e) {
            log.error("添加用户失败", e);
            throw e;
        }
    }
}

❌ 问题:
  - 日志代码重复 N 遍
  - 如果要改日志格式,要改 N 个地方
  - 日志逻辑和业务逻辑搅在一起,不好维护
✅ 有 AOP 后:专注业务,横切逻辑交给 Spring

@Aspect
@Component
public class LoggingAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("方法 {} 开始执行", pjp.getSignature());
        Object result = pjp.proceed(); // 执行业务代码
        log.info("方法 {} 执行完成", pjp.getSignature());
        return result;
    }
}

4.2 JDK 动态代理 vs CGLIB:两种实现原理

Spring AOP 支持两种代理方式,各有优劣:

🔀 两种代理方式对比

┌─────────────────┬────────────────────────┬────────────────────────┐
│                 │    JDK 动态代理          │      CGLIB             │
├─────────────────┼────────────────────────┼────────────────────────┤
│ 原理            │ 基于接口,运行时生成     │ 基于继承,运行时生成    │
│                 │ 实现类 $Proxy            │ 子类 MethodInterceptor │
├─────────────────┼────────────────────────┼────────────────────────┤
│ 要求            │ 目标类必须实现接口       │ 目标类不能用 final     │
│                 │                        │ 方法不能用 final        │
├─────────────────┼────────────────────────┼────────────────────────┤
│ 性能            │ 反射调用,略慢           │ 直接调用,快            │
├─────────────────┼────────────────────────┼────────────────────────┤
│ Spring 默认行为  │ 优先用 JDK(因为更轻量) │ 类没有接口时自动降级     │
└─────────────────┴────────────────────────┴────────────────────────┘

手动实现 JDK 动态代理

/**
 * JDK 动态代理示例
 *
 * 原理:
 *   1. 实现 InvocationHandler 接口
 *   2. 在 invoke() 方法里写增强逻辑
 *   3. 用 Proxy.newProxyInstance() 生成代理对象
 *
 * 限制:目标类必须实现接口。
 * 这就是为什么 Spring 推荐的service层用接口而不是实现类。
 */
public class JdkProxyDemo {

    // 目标接口
    interface UserService {
        void addUser(String name);
    }

    // 目标实现
    static class UserServiceImpl implements UserService {
        @Override
        public void addUser(String name) {
            System.out.println("真正执行业务:添加用户 " + name);
        }
    }

    // InvocationHandler:代理逻辑写在这里
    static class LoggingHandler implements InvocationHandler {
        private final Object target; // 真实对象

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

        /**
         * invoke 是代理方法,每次调用都会进这里
         *
         * @param proxy  生成的代理对象(一般不用)
         * @param method 正在调用的方法
         * @param args   方法参数
         * @return 方法返回值
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("[JDK代理] 方法 " + method.getName() + " 开始");
            long start = System.currentTimeMillis();

            // ⭐ 这里才真正调用目标对象的方法
            Object result = method.invoke(target, args);

            long cost = System.currentTimeMillis() - start;
            System.out.println("[JDK代理] 方法 " + method.getName() + " 结束,耗时 " + cost + "ms");
            return result;
        }
    }

    public static void main(String[] args) {
        UserService target = new UserServiceImpl();

        // 创建代理对象(这是关键!)
        UserService proxy = (UserService) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(), // 类加载器
                target.getClass().getInterfaces(),               // 实现哪些接口
                new LoggingHandler(target)                      // 代理逻辑
        );

        // 调用代理对象(增强逻辑在 invoke 里自动执行)
        proxy.addUser("张三");
        // 输出:
        // [JDK代理] 方法 addUser 开始
        // 真正执行业务:添加用户 张三
        // [JDK代理] 方法 addUser 结束,耗时 0ms
    }
}

手动实现 CGLIB 代理

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * CGLIB 动态代理示例
 *
 * 原理:
 *   1. 基于继承,不要求目标类实现接口
 *   2. 运行时生成目标类的子类,重写所有非 final 方法
 *   3. 在 intercept() 里写增强逻辑
 *
 * 注意:
 *   - 目标类不能是 final
 *   - 目标方法不能是 final 或 private
 */
public class CglibProxyDemo {

    static class UserService {
        public void addUser(String name) {
            System.out.println("真正执行业务:添加用户 " + name);
        }
    }

    static class LoggingInterceptor implements MethodInterceptor {
        /**
         * intercept:每次方法调用都会进来
         *
         * @param o        被代理的对象
         * @param method   正在调用的方法
         * @param args     方法参数
         * @param methodProxy 方法的代理对象(用于快速调用)
         */
        @Override
        public Object intercept(Object o, Method method, Object[] args,
                               MethodProxy methodProxy) throws Throwable {
            System.out.println("[CGLIB代理] Before: " + method.getName());
            long start = System.currentTimeMillis();

            // 两种调用方式:
            // 1. methodProxy.invokeSuper(o, args) ← 推荐,性能更好
            // 2. method.invoke(o, args) ← 性能略差

            Object result = methodProxy.invokeSuper(o, args);

            System.out.println("[CGLIB代理] After: " + method.getName()
                    + ", 耗时: " + (System.currentTimeMillis() - start) + "ms");
            return result;
        }
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);  // 设置父类
        enhancer.setCallback(new LoggingInterceptor()); // 设置拦截器

        UserService proxy = (UserService) enhancer.create();
        proxy.addUser("李四");
        // 输出:
        // [CGLIB代理] Before: addUser
        // 真正执行业务:添加用户 李四
        // [CGLIB代理] After: addUser, 耗时: 0ms
    }
}

4.3 Spring AOP 的执行流程

🎯 Spring AOP 执行全流程

   请求到达

   Step 1:判断是否需要代理
     → 目标方法匹配切点(@Before/@After/@Around……)?
       → 是:生成代理对象(JDK 或 CGLIB)
       → 否:返回原始对象

   Step 2:生成代理后,执行链如下(以 @Around 为例)

     → @Around 前置逻辑(around())
     → 拦截器链按顺序执行
     → 目标方法真正执行
     → @Around 后置逻辑(返回或异常)

   请求离开,带着增强后的返回值

Spring AOP 注解执行顺序(Spring 5.2.7+ 支持 @Order)

@Service
@Order(1)  // 数字越小越先执行
public class FirstAspect {
    @Before("...")
    public void first() {
        System.out.println("① FirstAspect @Before");
    }
}

@Service
@Order(2)
public class SecondAspect {
    @Before("...")
    public void second() {
        System.out.println("② SecondAspect @Before");
    }
}

// 输出顺序:
// ① FirstAspect @Before
// ② SecondAspect @Before
// ===== 目标方法执行 =====
// ② SecondAspect @AfterReturning
// ① FirstAspect @AfterReturning

五、事务机制:@Transactional 到底是怎么”保证”你的数据的?

5.1 事务传播行为:7 种策略

Spring 事务的核心概念是传播行为(Propagation),它定义了”当一个事务方法被另一个事务方法调用时,这个方法应该在什么事务里运行”。

/**
 * 事务传播行为的 7 种策略
 *
 * 这是 Spring 事务最核心的概念,也是面试高频点。
 *
 * 记忆技巧:
 *   REQUIRED  = 借别人的(最常用)
 *   REQUIRES_NEW = 用自己的(独立事务)
 *   NESTED = 嵌套事务(保存点机制)
 *   SUPPORTS / NOT_SUPPORTED / MANDATORY / NEVER = 特殊场景
 */
public enum Propagation {

    /**
     * REQUIRED(默认,最常用)
     *
     * 如果当前有事务,就加入这个事务
     * 如果当前没有事务,就创建一个新事务
     *
     * 场景:大多数业务方法都用这个
     */
    REQUIRED,

    /**
     * REQUIRES_NEW
     *
     * 总是创建新事务
     * 如果当前有事务,把当前事务挂起
     *
     * 场景:日志记录、消息发送等不影响主事务的操作
     */
    REQUIRES_NEW,

    /**
     * NESTED
     *
     * 如果当前有事务,就在当前事务里嵌套一个子事务
     * 使用数据库的保存点(savepoint)实现
     * 场景:需要部分回滚时用(比如批量处理,部分失败只回滚部分)
     */
    NESTED,

    /**
     * SUPPORTS
     *
     * 如果当前有事务,就加入
     * 如果没有,就以非事务方式运行
     */
    SUPPORTS,

    /**
     * NOT_SUPPORTED
     *
     * 以非事务方式运行
     * 如果当前有事务,把当前事务挂起
     */
    NOT_SUPPORTED,

    /**
     * MANDATORY
     *
     * 必须在事务中运行
     * 如果当前没有事务,抛异常
     */
    MANDATORY,

    /**
     * NEVER
     *
     * 必须在非事务中运行
     * 如果当前有事务,抛异常
     */
    NEVER
}

5.2 七大事务失效场景:为什么 @Transactional 加了不生效?

这是实操中最容易踩坑的地方。七大事务失效场景

场景一:非 public 方法

@Transactional
private void save() {  // ❌ 不生效
    dao.insert();
}

原因:Spring AOP 基于代理,private 方法不会被代理覆盖。


场景二:同类内部调用(self-invocation)

@Service
public class OrderService {
    public void create() {
        this.save();  // ❌ 绕过了代理,事务不生效
    }

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

原因:this 调用的是原始对象,不是代理对象。
解决:用 AopContext.currentProxy() 拿到代理对象调用,或拆到另一个 Bean 里。


场景三:异常被 catch 吃了

@Transactional
public void transfer() {
    try {
        dao.debit();
        dao.credit();
    } catch (Exception e) {
        // ❌ 异常被吞了,事务不知道要回滚
    }
}

解决:catch 里重新 throw,或 catch 具体异常而非 Exception


场景四:异常类型不对

@Transactional  // 默认只对 RuntimeException 回滚
public void doSomething() {
    if (condition) throw new BusinessException(); // 自定义 checked 异常
}

解决:@Transactional(rollbackFor = Exception.class)


场景五:多数据源没配置事务管理器

@Transactional(transactionManager = "orderDataSourceTxManager")
public void createOrder() { ... }

解决:多数据源时,显式指定 transactionManager


场景六:数据库本身不支持事务

MySQL MyISAM 引擎不支持事务,换 InnoDB。
检查:SHOW TABLE STATUS WHERE Name = 'xxx';


场景七:timeout 设置过短

@Transactional(timeout = 1)  // 1 秒超时,业务被强制回滚
public void longOperation() {
    // 超过 1 秒就凉了
}

解决:根据实际业务耗时合理设置 timeout 值。

5.3 编程式事务:细粒度控制

除了 @Transactional,Spring 还提供了编程式事务(TransactionTemplate),适合复杂场景:

/**
 * 编程式事务示例
 *
 * 适用场景:
 *   - 需要精确控制事务边界(比如一个方法里部分要事务,部分不要)
 *   - 需要根据条件决定是否提交
 *   - 事务中有多个不同数据源
 */
@Service
@RequiredArgsConstructor
public class TransferService {

    private final TransactionTemplate transactionTemplate;
    private final AccountDao accountDao;

    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // execute 的 lambda 里的代码,就在事务里执行
        transactionTemplate.executeWithoutResult(status -> {
            try {
                // 扣款
                accountDao.decreaseBalance(fromId, amount);

                // 模拟网络延迟
                Thread.sleep(2000);

                // 收款
                accountDao.increaseBalance(toId, amount);

            } catch (Exception e) {
                // 编程式事务可以在任何地方回滚
                status.setRollbackOnly();
                throw new BizException("转账失败:" + e.getMessage());
            }
        });
    }
}

六、事件机制:Spring 的发布-订阅模式

6.1 为什么需要事件机制?

假设你写了一个下单功能,下单成功后需要:发短信通知、给用户加积分、发邮件、记录日志……如果全写在一个方法里:

❌ 紧耦合写法

public void createOrder(Order order) {
    // 下单核心逻辑
    orderDao.insert(order);

    // 发短信(耦合进来)
    smsService.send(order.getPhone(), "下单成功");

    // 加积分(耦合进来)
    pointService.add(order.getUserId(), 100);

    // 发邮件(耦合进来)
    mailService.send(order.getEmail(), "订单确认");

    // 记录日志(耦合进来)
    log.info("用户 {} 下单成功", order.getUserId());
}

问题:
  - 每加一个功能就要改这个方法
  - 短信/积分/邮件 失败,会影响下单主流程
  - 无法单独测试每个功能
✅ 事件驱动写法

public void createOrder(Order order) {
    // 下单核心逻辑
    orderDao.insert(order);

    // 发布事件(解耦!)
    applicationEventPublisher.publishEvent(new OrderCreatedEvent(order));
}

// 短信监听器
@Component
public class SmsListener {

    @EventListener  // 自动订阅 OrderCreatedEvent
    public void handle(OrderCreatedEvent event) {
        smsService.send(event.getOrder().getPhone(), "下单成功");
    }
}

// 积分监听器
@Component
public class PointListener {
    @EventListener
    public void handle(OrderCreatedEvent event) {
        pointService.add(event.getOrder().getUserId(), 100);
    }
}

6.2 异步事件:不让监听器拖慢主流程

/**
 * 异步事件监听器
 *
 * 场景:发邮件、发短信等耗时操作,不需要同步等待完成
 *
 * 注意:需要在启动类加 @EnableAsync 才生效
 */
@Component
public class AsyncEventListener {

    /**
     * @Async = 异步执行,不阻塞主线程
     * 注意:异步方法不能返回 Object,必须是 void
     */
    @Async
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        // 发邮件这种耗时操作,放异步里处理
        mailService.send(event.getOrder().getEmail(), "订单确认");
        log.info("异步发送邮件完成");
    }
}

七、自动装配:Spring 是怎么”猜到”你想注入什么的?

7.1 @Conditional:按条件注册 Bean

Spring Boot 的自动装配核心就是 @Conditional。它让你可以”只在满足某个条件时才注册这个 Bean”。

/**
 * Spring Boot 自动装配的核心注解
 *
 * 原理:@Configuration + @Conditional 控制这个配置类要不要生效
 *
 * Spring Boot 自动装配示例(模拟 Spring Boot 的自动配置原理)
 */
@Configuration
public class MyAutoConfiguration {

    /**
     * 当类路径上有 DruidDataSource 这个类时,
     * 才注册这个 DataSource Bean
     */
    @ConditionalOnClass(name = "com.alibaba.druid.pool.DruidDataSource")
    @Bean
    public DataSource druidDataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/test");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }

    /**
     * 当没有用户自定义的 DataSource 时,才注册默认的
     */
    @ConditionalOnMissingBean(DataSource.class)
    @Bean
    public DataSource defaultDataSource() {
        return new HikariDataSource(); // Spring Boot 默认连接池
    }

    /**
     * 只有当配置文件中 spring.cache.type = redis 时才生效
     */
    @ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
    @Bean
    public RedisCacheManager redisCacheManager() {
        return new RedisCacheManager(redisTemplate());
    }
}

7.2 Spring Boot 的自动装配流程

🔄 Spring Boot 自动装配流程

   1. @SpringBootApplication = @EnableAutoConfiguration
                              ↓
   2. Spring Boot 通过 META-INF/spring.factories
      或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
      加载所有 AutoConfiguration 类
                              ↓
   3. 每个 AutoConfiguration 类用 @Conditional 筛选
                              ↓
   4. 符合条件的 Bean 注册到容器
                              ↓
   5. 用户自定义配置优先级 > 自动配置
                              ↓
   6. 完成!容器里有所有需要的服务

八、总结:Spring 高级特性全景图

🎯 Spring 高级特性速查表

┌────────────┬──────────────────────────────┬─────────────────────────┐
│ 特性       │ 核心原理                      │ 实战要点                │
├────────────┼──────────────────────────────┼─────────────────────────┤
│ Bean生命周期│ 实例化→填充→初始化→代理→使用  │ @PostConstruct 初始化    │
│            │                              │ destroy-method 清理      │
├────────────┼──────────────────────────────┼─────────────────────────┤
│ 循环依赖   │ 三级缓存:singletonFactories  │ 构造器注入无法解决       │
│            │ → earlySingletonObjects      │ 用 @Lazy 打破循环        │
│            │ → singletonObjects           │                         │
├────────────┼──────────────────────────────┼─────────────────────────┤
│ AOP 代理   │ JDK 动态代理(基于接口)       │ private 方法不生效       │
│            │ CGLIB(基于继承)             │ 同类内部调用不生效       │
│            │                              │ 用 order 控制执行顺序    │
├────────────┼──────────────────────────────┼─────────────────────────┤
│ 事务传播   │ REQUIRED=借别人的事务(默认)  │ 异常被 catch 不回滚      │
│            │ REQUIRES_NEW=独立新事务       │ rollbackFor 要指定       │
│            │ NESTED=嵌套事务              │ 数据库要支持事务         │
├────────────┼──────────────────────────────┼─────────────────────────┤
│ 事件机制   │ ApplicationEvent +           │ @Async 异步监听          │
│            │ @EventListener               │ 发布者和监听者完全解耦   │
├────────────┼──────────────────────────────┼─────────────────────────┤
│ 自动装配   │ @Conditional 条件注册          │ 用户配置优先于自动配置   │
│            │ META-INF/spring.factories    │ 多个条件用 @Conditional  │
└────────────┴──────────────────────────────┴─────────────────────────┘

 

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注