🔗 Spring 生态因果链

因为…所以… — 从手动new对象到自动装配的完整因果推导

IoC DI AOP @Transactional 自动装配 Starter 循环依赖 Bean生命周期

📊 因果链总览图

❓ 手动new对象
耦合严重
💡 工厂模式 ⚡ 工厂也耦合 💡 IoC容器
(反射+工厂)
⚡ Bean需要依赖 💡 DI依赖注入 ⚡ 横切关注点
日志/事务重复
💡 AOP动态代理
⚡ AOP+方法拦截 💡 @Transactional ⚡ 配置太多 💡 自动装配
Spring Boot
⚡ 通用功能重复 💡 自定义Starter ⚡ 循环依赖 💡 三级缓存
Spring 演进因果链
手动new = 类自己创建依赖对象,耦合度最高 → 工厂模式 = 统一创建对象,减少重复代码 → IoC = 容器统一管理对象创建,类只声明需要什么 → DI = 容器自动注入依赖,无需手动获取 → AOP = 横切逻辑(日志/事务)统一织入,不改业务代码 → @Transactional = AOP在事务场景的具体应用 → 自动装配 = 根据类型/名称自动匹配依赖 → Starter = 一键引入所有相关依赖和配置

① 因为:手动new对象 → 代码耦合严重

❓ 问题:每个对象都自己创建依赖
// ❌ 直接new:UserService 和 UserDao 紧耦合 public class UserService { private UserDao userDao = new UserDaoImpl(); // 写死了! public User getUser(Long id) { return userDao.findById(id); } } // 想换成 UserDaoMongoImpl?改源码! // 想做单元测试 mock?改源码!

每次需要换实现,都要改new那一行。代码像一团乱麻,牵一发而动全身。

💡 所以:IoC容器(控制反转)

控制权从"自己new"反转到"容器给你"。通过反射,Spring容器在运行时动态创建对象并注入依赖。

// ✅ IoC:不再自己new,让Spring容器给你 @Service public class UserService { @Autowired // Spring自动注入,不再new private UserDao userDao; // 接口!不是实现类! public User getUser(Long id) { return userDao.findById(id); } } @Repository public class UserDaoImpl implements UserDao { ... } // 想换实现?新写一个类加@Repository就行,不用改UserService!

IoC的核心:反射——Spring不import你的类,而是通过反射在运行时创建:

// Spring IoC 底层原理(简化版) public class SimpleIocContainer { private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); public void registerBean(String name, Class<?> clazz) throws Exception { // 反射创建实例 Object instance = clazz.getDeclaredConstructor().newInstance(); singletonObjects.put(name, instance); } public Object getBean(String name) { return singletonObjects.get(name); } } // 使用 SimpleIocContainer container = new SimpleIocContainer(); container.registerBean("userService", UserService.class); UserService service = (UserService) container.getBean("userService");
⚡ 但是:Bean有依赖怎么注入?

UserService依赖UserDao,容器创建UserService时,怎么知道要注入哪个UserDao? → 引出DI依赖注入

② 因为:Bean有依赖 → 需要自动注入

❓ 问题:IoC创建了对象,但依赖关系怎么办?

容器知道有哪些Bean,但不知道谁依赖谁。手动配置依赖?那又回到了XML地狱。

💡 所以:DI依赖注入(三种方式)
// 方式1:构造器注入(✅ Spring推荐) @Service public class UserService { private final UserDao userDao; private final EmailService emailService; @Autowired // 构造器只有一个参数时可省略 public UserService(UserDao userDao, EmailService emailService) { this.userDao = userDao; this.emailService = emailService; } } // 方式2:Setter注入 @Service public class OrderService { private PaymentService paymentService; @Autowired public void setPaymentService(PaymentService paymentService) { this.paymentService = paymentService; } } // 方式3:字段注入(❌ 不推荐,但最常见) @Service public class ProductService { @Autowired // 反射直接注入字段 private ProductDao productDao; }

为什么推荐构造器注入?

注入方式不可变性空安全测试友好循环依赖
构造器注入✅ final字段✅ 编译期检查✅ 容易mock❌ 直接报错
Setter注入❌ 可变⚠️ 运行时NPE✅ 可选依赖⚠️ 可能不报错
字段注入❌ 可变❌ 运行时NPE❌ 反射才能mock⚠️ 隐藏问题
⚡ 但是:有些功能每个Bean都需要(日志/事务/权限)→ 代码重复

每个Service都要写log.info()beginTransaction()checkPermission(),这些横切关注点散落在所有类中 → 引出AOP

③ 因为:横切关注点重复 → 需要AOP

❓ 问题:每个方法都要写重复的非业务逻辑
// ❌ 没有AOP:每个方法都重复写日志、权限、事务 public void createOrder(OrderDTO dto) { log.info("开始创建订单"); // 日志 checkPermission("order:create"); // 权限 try { transactionManager.begin(); // 事务 orderDao.insert(dto); // 真正的业务逻辑 transactionManager.commit(); } catch (Exception e) { transactionManager.rollback(); // 事务回滚 log.error("创建订单失败", e); // 日志 } } // 50个Service方法 → 日志+权限+事务 重复50次!
💡 所以:AOP(面向切面编程)——动态代理

因为所有方法都要写重复的非业务代码,所以Spring用动态代理,在方法调用前后自动加上这些逻辑。

调用 orderService.createOrder()
代理对象
(Spring自动生成)
① 记录日志 ② 开启事务 ③ 真正执行业务方法
④ 提交/回滚事务 ⑤ 记录结果日志
// AOP 切面:自动加日志 @Aspect @Component public class LogAspect { @Around("execution(* com.example.service..*.*(..))") public Object logAround(ProceedingJoinPoint pjp) throws Throwable { String methodName = pjp.getSignature().getName(); log.info("开始执行: {}", methodName); try { Object result = pjp.proceed(); // 执行真正的方法 log.info("执行成功: {}", methodName); return result; } catch (Exception e) { log.error("执行失败: {}", methodName, e); throw e; } } } // 业务代码终于干净了! @Service public class OrderService { public void createOrder(OrderDTO dto) { orderDao.insert(dto); // 只有业务逻辑! } }

AOP的底层:JDK动态代理 vs CGLIB

JDK动态代理CGLIB代理
原理实现接口, InvocationHandler继承类,生成子类
要求目标类必须实现接口目标类不能是final
Spring默认接口用JDK代理类用CGLIB代理
Spring Boot默认全部CGLIB(spring.aop.proxy-target-class=true)
类比:交叉路口装摄像头 vs 每辆车里装记录仪
没有AOP = 每辆车里装行车记录仪(每段代码里写日志)→ 又贵又乱
AOP = 在交叉路口装摄像头(在切点自动拦截)→ 一个摄像头管所有车
⚡ 但是:AOP有个致命限制——内部调用不走代理

同一个类中方法A调用方法B,B上的AOP注解不生效!因为this.methodB()调的是原始对象,不是代理对象。

@Service public class OrderService { public void methodA() { this.methodB(); // ❌ @Transactional不会生效! // 因为this是原始对象,不是代理对象 } @Transactional public void methodB() { ... } } // 解决方案: // 1. 把methodB抽到另一个Service // 2. 注入自身:@Autowired OrderService self; self.methodB(); // 3. AopContext.currentService()

④ 因为:AOP能拦截方法 → 所以有了声明式事务

❓ 问题:手动管理事务太繁琐
// ❌ 手动管理事务:每个方法都要写 public void transfer(Long from, Long to, BigDecimal amount) { TransactionStatus status = null; try { status = txManager.getTransaction(new DefaultTransactionDefinition()); accountDao.debit(from, amount); accountDao.credit(to, amount); txManager.commit(status); } catch (Exception e) { if (status != null) txManager.rollback(status); throw e; } }
💡 所以:@Transactional——因为AOP能拦截,所以一个注解搞定事务
// ✅ 一个注解代替20行事务代码 @Transactional public void transfer(Long from, Long to, BigDecimal amount) { accountDao.debit(from, amount); accountDao.credit(to, amount); // 出异常自动回滚! }

@Transactional的因果链:因为AOP → 所以能拦截方法 → 所以能在方法前后加事务逻辑 → 所以只需要一个注解

⚡ 但是:@Transactional有7种失效场景

因为@Transactional依赖AOP代理,所以所有绕过代理的场景都会失效:

#失效场景原因修复
1同类内部调用this调用绕过代理拆到另一个Service
2方法非publicCGLIB不代理非public改为public
3方法final/staticCGLIB无法重写去掉final/static
4异常被catch吞没抛给代理对象catch后re-throw
5检查异常未指定默认只回滚RuntimeExceptionrollbackFor=Exception.class
6Bean未被Spring管理没走IoC容器加@Service/@Component
7传播行为配置错REQUIRES_NEW挂起外层检查propagation
// ❌ 失效案例1:异常被吞 @Transactional public void transfer(Long from, Long to, BigDecimal amount) { try { accountDao.debit(from, amount); accountDao.credit(to, amount); } catch (Exception e) { log.error("转账失败", e); // ❌ 异常被catch了,没抛出去 → 代理不知道 → 不回滚! } } // ✅ 修复:catch后重新抛出 @Transactional(rollbackFor = Exception.class) // 检查异常也回滚 public void transfer(Long from, Long to, BigDecimal amount) { try { accountDao.debit(from, amount); accountDao.credit(to, amount); } catch (Exception e) { log.error("转账失败", e); throw new RuntimeException(e); // ✅ 重新抛出 } }
类比:@Transactional失效
事务就像银行转账的"确认按钮"——只有柜台人员(代理对象)才能按。你自己打电话(内部调用this)给柜员说"确认了",银行系统(Spring)根本不知道!

⑤ 因为:XML配置太多 → 所以自动装配

❓ 问题:Spring时代的XML配置地狱
<!-- 传统Spring: 每个Bean都要在XML里配 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db"/> <property name="user" value="root"/> <property name="password" value="xxx"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 一个中大型项目 500+ 行 XML -->
💡 所以:Spring Boot自动装配——约定大于配置

因果链:因为XML太多 → 所以用注解代替 → 但注解也要一个一个配 → 所以约定大于配置,按约定自动配

// Spring Boot 自动装配的因果链: // 1. @SpringBootApplication 包含 @EnableAutoConfiguration // 2. @EnableAutoConfiguration 通过 @Import 加载 AutoConfigurationImportSelector // 3. AutoConfigurationImportSelector 读取 META-INF/spring.factories // 4. spring.factories 里列出了所有自动配置类 // 5. 每个自动配置类用 @Conditional 系列注解判断是否生效 // 自动配置类示例 @AutoConfiguration @ConditionalOnClass(DataSource.class) // 因为classpath有DataSource @ConditionalOnMissingBean(DataSource.class) // 因为没有手动配DataSource public class DataSourceAutoConfiguration { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().build(); } } // 所以:只要引入依赖 + 写配置文件,Bean就自动创建了!

@Conditional 系列注解——自动装配的核心开关

注解含义使用场景
@ConditionalOnClassclasspath有这个类引入依赖才生效
@ConditionalOnMissingBean容器中没有这个Bean用户没手动配才自动配
@ConditionalOnProperty配置文件有这个属性开关控制
@ConditionalOnWebApplication是Web应用Web相关配置

⑥ 因为:通用功能每个项目都要配 → 所以Starter

❓ 问题:每个项目都要重复配Redis、MQ、数据源...

10个微服务项目,每个都要配Redis连接池、序列化、超时时间... 复制粘贴10次?改一个参数要改10个项目?

💡 所以:自定义Starter——把通用功能封装成"一键启动包"
// 自定义Starter结构 my-redis-spring-boot-starter/ ├── pom.xml // 依赖 spring-boot-starter + redis客户端 └── src/main/resources/ └── META-INF/ └── spring.factories // 声明自动配置类 └── src/main/java/ └── com/example/redis/ ├── RedisAutoConfiguration.java // 自动配置类 ├── RedisProperties.java // 配置属性 └── RedisService.java // 封装好的Redis操作 // RedisAutoConfiguration @AutoConfiguration @ConditionalOnClass(RedisTemplate.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean public RedisTemplate<String, Object> redisTemplate( RedisConnectionFactory factory, RedisProperties props) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 默认用Jackson序列化 template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer()); return template; } } // 使用方只需: // 1. pom.xml 加 <dependency>my-redis-spring-boot-starter</dependency> // 2. application.yml 加 spring.redis.host=xxx // 完事!RedisTemplate直接@Autowired注入

⑦ 因为:DI依赖注入 → 所以可能出现循环依赖

❓ 问题:A依赖B,B又依赖A → 先创建谁?
ServiceA → 依赖 ServiceB → 依赖 ServiceA
先创建A?A需要B,B还没创建!
先创建B?B需要A,A还没创建!
💡 所以:三级缓存
// Spring 三级缓存解决循环依赖 // 一级缓存:singletonObjects → 完整的Bean // 二级缓存:earlySingletonObjects → 早期引用(可能被AOP代理) // 三级缓存:singletonFactories → Bean工厂(用于生成代理) // 创建流程(A → B → A循环): // 1. 创建A,实例化后放入三级缓存(ObjectFactory) // 2. A发现需要B,去创建B // 3. B发现需要A,从三级缓存获取A的早期引用 // (如果A需要AOP代理,ObjectFactory会提前生成代理对象) // 4. B拿到A的早期引用,完成创建 // 5. A拿到B,完成创建 // 6. A和B都创建完毕 // 为什么需要三级?二级不够吗? // 因为AOP!如果A需要代理,ObjectFactory才能生成代理对象 // 二级缓存只存引用,不能判断是否需要代理 // 三级缓存的ObjectFactory调用时才决定:普通Bean直接返回,AOP Bean返回代理
⚠️ Spring Boot 2.6+ 默认禁止循环依赖
spring.main.allow-circular-references=false(默认)
因为循环依赖本身就是设计问题,应该通过重构解决。

⑧ 因为:IoC管理Bean → 所以需要Bean生命周期

❓ 问题:Bean创建过程中需要各种扩展点

创建Bean不是简单的new——需要注入依赖、处理AOP、执行初始化逻辑、销毁时清理资源。这些都需要在特定时机执行。

💡 所以:Bean生命周期——从创建到销毁的完整流程
1.实例化
(反射new)
2.属性赋值
(@Autowired)
3.BeanNameAware
BeanFactoryAware
4.BeanPostProcessor
前置处理
5.InitializingBean
afterPropertiesSet
6.@PostConstruct
自定义init
7.BeanPostProcessor
后置处理(AOP)
8.Bean就绪
可使用
9.@PreDestroy
DisposableBean

因果链视角:因为IoC要管理Bean → 所以需要生命周期 → 因为AOP要在Bean创建后代理 → 所以BeanPostProcessor是关键扩展点

@Component public class MyBean implements InitializingBean, DisposableBean, BeanNameAware, ApplicationContextAware { @Autowired private SomeDependency dep; // 第2步:属性赋值 @Override public void setBeanName(String name) { // 第3步:Aware回调 log.info("我的名字是: {}", name); } @Override public void setApplicationContext(ApplicationContext ctx) { log.info("拿到了ApplicationContext"); } @PostConstruct // 第6步:自定义初始化 public void init() { log.info("@PostConstruct 初始化"); } @Override public void afterPropertiesSet() { // 第5步:接口初始化 log.info("afterPropertiesSet 初始化"); } @PreDestroy // 第9步:销毁前 public void cleanup() { log.info("@PreDestroy 清理资源"); } @Override public void destroy() { log.info("DisposableBean 销毁"); } } // 输出顺序: // 1. 实例化 (构造器) // 2. 属性赋值 (@Autowired) // 3. setBeanName // 4. setApplicationContext // 5. @PostConstruct // 6. afterPropertiesSet // 7. BeanPostProcessor后置处理 (AOP代理在这里) // -- Bean就绪,可使用 -- // 8. @PreDestroy // 9. destroy

🎯 面试点映射:因果链视角

面试题因果链位置因为…所以…
IoC原理 第①环 因为手动new耦合 → 所以IoC用反射+工厂解耦
DI三种注入 第②环 因为IoC创建了对象但不知道依赖 → 所以DI自动注入
AOP原理 第③环 因为横切关注点重复 → 所以AOP用动态代理拦截
JDK代理vs CGLIB 第③环 因为有些类没接口 → 所以需要CGLIB继承代理
@Transactional失效 第④环 因为@Transactional依赖代理 → 所以内部调用不走代理就失效
自动装配原理 第⑤环 因为XML配置太多 → 所以@Conditional按条件自动装配
自定义Starter 第⑥环 因为通用功能每个项目重复配 → 所以封装成Starter一键引入
循环依赖 第⑦环 因为DI互相依赖 → 所以三级缓存提前暴露早期引用
Bean生命周期 第⑧环 因为IoC管理Bean全流程 → 所以提供Aware/PostProcessor等扩展点
三级缓存 第⑦环 因为AOP代理需要在运行时生成 → 所以用ObjectFactory延迟生成代理
面试答题模板(因果推导法):
❌ "Spring AOP用的是JDK动态代理和CGLIB"(只说结论)
✅ "因为手动new对象耦合严重,所以有了IoC容器;因为IoC只管创建不管依赖,所以有了DI;因为每个方法都要写日志事务等重复代码,所以Spring用AOP动态代理来拦截——而代理的实现有两种:有接口用JDK动态代理,没接口用CGLIB子类继承"(从根因一步步推导)

📌 因果链总结

手动new
耦合
IoC
反射+工厂
Bean有
依赖
DI
自动注入
横切关注
重复
AOP
动态代理
@Transactional
声明式事务
配置
太多
自动装配
Spring Boot
通用功能
重复
自定义
Starter
循环
依赖
三级
缓存
全栈资料库 · 因果链板块 · Spring生态因果链