📊 因果链总览图
❓ 手动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 | 方法非public | CGLIB不代理非public | 改为public |
| 3 | 方法final/static | CGLIB无法重写 | 去掉final/static |
| 4 | 异常被catch吞 | 没抛给代理对象 | catch后re-throw |
| 5 | 检查异常未指定 | 默认只回滚RuntimeException | rollbackFor=Exception.class |
| 6 | Bean未被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 系列注解——自动装配的核心开关
| 注解 | 含义 | 使用场景 |
| @ConditionalOnClass | classpath有这个类 | 引入依赖才生效 |
| @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