开场:为什么你写的 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.factories或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports自动注册进来的。
二、Bean 生命周期:Spring 到底对你写的类做了什么?
每个 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)。
很多人分不清这两个,这里直接说清楚:
| 特性 | @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")
}
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 是怎么解决”鸡生蛋、蛋生鸡”问题的?
循环依赖,就是两个(或多个)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 → 死循环 → 报错。
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 能继续往下走。循环就此打破。
这是面试高频题,我直接说结论:
为什么不能只用二级缓存?
二级缓存只能解决”普通 Bean”的循环依赖,但如果 A 需要被 AOP 增强,问题就来了:
-
B 需要注入 A 时,A 必须是代理对象(才能加事务) -
但此时 A 还在创建中,Spring 还没决定要不要给它加代理 -
二级缓存只能存”对象”,不能”延迟决定要不要代理”
三级缓存做了什么?
-
三级存的不是对象,是”创建代理的工厂”( ObjectFactory) -
每次从三级取,调用工厂 → 拿到的是代理对象 -
代理对象放进二级,B 拿到的是带 AOP 功能的 A
| 缓存 | 存什么 | 干什么 |
|---|---|---|
| 一级 | 完全体 Bean | 交付给所有线程 |
| 二级 | 半成品 Bean | 循环依赖时应急 |
| 三级 | 代理工厂 | 延迟决定要不要代理 |
@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 是怎么”偷偷”给你加代码的?
业务代码里,总有一些”横切关注点”(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;
}
}
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
}
}
🎯 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 到底是怎么”保证”你的数据的?
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
}
这是实操中最容易踩坑的地方。七大事务失效场景:
场景一:非 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 值。
除了 @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 的发布-订阅模式
假设你写了一个下单功能,下单成功后需要:发短信通知、给用户加积分、发邮件、记录日志……如果全写在一个方法里:
❌ 紧耦合写法
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);
}
}
/**
* 异步事件监听器
*
* 场景:发邮件、发短信等耗时操作,不需要同步等待完成
*
* 注意:需要在启动类加 @EnableAsync 才生效
*/
@Component
public class AsyncEventListener {
/**
* @Async = 异步执行,不阻塞主线程
* 注意:异步方法不能返回 Object,必须是 void
*/
@Async
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 发邮件这种耗时操作,放异步里处理
mailService.send(event.getOrder().getEmail(), "订单确认");
log.info("异步发送邮件完成");
}
}
七、自动装配:Spring 是怎么”猜到”你想注入什么的?
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());
}
}
🔄 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 │
└────────────┴──────────────────────────────┴─────────────────────────┘