Spring 从零到精通验收清单
这页用来验收你是否真正学懂 Spring。不是会写 @Service、@Autowired、@Transactional 就算掌握,而是要能解释:一个普通 Java 类为什么会变成 Bean,Bean 为什么能被注入,为什么有时拿到的是代理对象,事务为什么会失效,扩展点为什么能支撑 Spring Boot 自动配置、MyBatis Mapper、Feign Client、Dubbo 服务引用。
Spring 的学习主线是一句话:
Spring 先把类变成 BeanDefinition,再由容器按生命周期创建 Bean,通过依赖注入组装对象,通过后置处理器生成代理,通过 AOP 实现事务和横切增强,通过扩展点让框架接入容器。
最终目标
| 能力 | 达标标准 |
|---|---|
| 零基础能懂 | 能从“为什么不要到处 new 对象”讲到 IOC 容器 |
| 原理能讲 | 能画出 BeanDefinition、Bean 生命周期、三级缓存、AOP、事务链路 |
| Demo 能写 | 能写 IOC、AOP、声明式事务、编程式事务、事件、扩展点 Demo |
| 项目能落地 | 能解释订单、支付、采集、资产目录系统里 Spring 怎么组织业务 |
| 故障能排查 | 能排查注入失败、代理失效、事务不回滚、循环依赖、Bean 创建失败 |
| 面试能闭环 | 面试页给标准回答,知识点页讲清原理和失败边界 |
总路线
flowchart TD
A["为什么需要 Spring"] --> B["BeanDefinition 元信息"]
B --> C["BeanFactory 和 ApplicationContext"]
C --> D["Bean 生命周期"]
D --> E["依赖注入和自动装配"]
E --> F["循环依赖和三级缓存"]
F --> G["AOP 代理"]
G --> H["声明式事务和编程式事务"]
H --> I["事件机制和扩展点"]
I --> J["商业场景和生产排查"]阶段 1:为什么需要 Spring
没有 Spring 会怎样
没有 Spring 时,业务对象经常这样创建:
public class OrderService {
private final OrderRepository orderRepository = new JdbcOrderRepository();
private final PayClient payClient = new HttpPayClient();
}短期看很直接,长期会出现:
| 问题 | 后果 |
|---|---|
| 依赖写死 | 测试时难替换,接入新实现要改业务类 |
| 生命周期分散 | 初始化连接、关闭资源、缓存预热散落各处 |
| 横切逻辑重复 | 日志、事务、权限、监控每个方法都写 |
| 配置不统一 | 不同模块自己读配置,生产排查困难 |
| 框架难集成 | MyBatis、事务、定时任务、消息监听难统一管理 |
Spring 的做法是让业务类只声明“我依赖什么”,容器负责“怎么创建、何时创建、注入谁、何时销毁、是否代理”。
最小 IOC Demo
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
interface PayClient {
void pay(String orderNo);
}
class MockPayClient implements PayClient {
public void pay(String orderNo) {
System.out.println("pay " + orderNo);
}
}
class OrderService {
private final PayClient payClient;
OrderService(PayClient payClient) {
this.payClient = payClient;
}
void createOrder(String orderNo) {
payClient.pay(orderNo);
}
}
@Configuration
class AppConfig {
@Bean
PayClient payClient() {
return new MockPayClient();
}
@Bean
OrderService orderService(PayClient payClient) {
return new OrderService(payClient);
}
}
public class IocDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean(OrderService.class);
orderService.createOrder("O1001");
context.close();
}
}这段代码里,OrderService 不自己 new PayClient,而是由 Spring 组装依赖。这就是 IOC 的第一层价值。
详细知识点:Spring IOC。
阶段 2:BeanDefinition 是 Spring 的图纸
是什么
Spring 不会扫描到一个类就立即 new。它会先把 Bean 的信息解析成 BeanDefinition。BeanDefinition 可以理解为创建 Bean 的图纸。
BeanDefinition 里通常包含:
- Bean 的 class。
- Bean 名称。
- 作用域:singleton、prototype 等。
- 是否懒加载。
- 构造参数。
- 属性依赖。
- 初始化方法。
- 销毁方法。
- 是否候选自动装配。
为什么需要 BeanDefinition
如果没有 BeanDefinition,Spring 就只能边扫描边创建对象,很多能力都做不了:
| 能力 | 为什么依赖 BeanDefinition |
|---|---|
| 条件注册 | 创建对象前先判断要不要注册 |
| 依赖排序 | 先知道依赖关系,再决定创建顺序 |
| 循环依赖处理 | 容器需要知道哪些 Bean 正在创建 |
| AOP 代理 | 后置处理器要有机会替换最终对象 |
| Spring Boot 自动配置 | 自动配置本质上也是注册 BeanDefinition |
| MyBatis Mapper | 扫描接口后动态注册 Mapper 代理定义 |
BeanDefinition 流程
flowchart TD
A["读取配置类或扫描包"] --> B["识别 Component 或 Bean 方法"]
B --> C["解析成 BeanDefinition"]
C --> D["注册到 BeanDefinitionRegistry"]
D --> E["BeanFactory 根据定义创建 Bean"]
E --> F["放入单例池或按作用域返回"]详细知识点:Bean生命周期。
阶段 3:BeanFactory 和 ApplicationContext
两者关系
BeanFactory 是 Spring IOC 容器的基础接口,核心能力是获取 Bean、管理 Bean 定义和创建流程。ApplicationContext 是更完整的应用上下文,在 BeanFactory 基础上增加了国际化、事件、资源加载、环境配置等能力。
| 对比 | BeanFactory | ApplicationContext |
|---|---|---|
| 定位 | 基础容器 | 应用级容器 |
| Bean 创建 | 支持 | 支持 |
| 事件机制 | 基础能力弱 | 内置事件发布和监听 |
| 资源加载 | 较基础 | 更完整 |
| 国际化 | 较弱 | 支持 |
| 常用程度 | 框架底层 | 业务项目常用 |
不这样理解会怎样
如果把 Spring 简单理解成“一个 Map”,会解释不了:
- 为什么 Bean 创建前可以修改 BeanDefinition。
- 为什么某些 Bean 初始化后变成代理。
- 为什么事件监听器能收到容器事件。
- 为什么 Spring Boot 能在启动早期加载 Environment。
- 为什么容器关闭时能执行销毁回调。
阶段 4:Bean 生命周期全过程
Bean 生命周期是 Spring 原理中心。注入失败、循环依赖、AOP 代理、事务失效、初始化顺序,都能放到这个流程里定位。
flowchart TD
A["BeanDefinition"] --> B["实例化对象"]
B --> C["属性填充和依赖注入"]
C --> D["Aware 回调"]
D --> E["BeanPostProcessor 前置处理"]
E --> F["初始化方法"]
F --> G["BeanPostProcessor 后置处理"]
G --> H["可能返回代理对象"]
H --> I["放入单例池"]
I --> J["容器关闭执行销毁"]生命周期 Demo
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
class LifeService implements BeanNameAware, InitializingBean, DisposableBean {
public LifeService() {
System.out.println("1 construct");
}
public void setBeanName(String name) {
System.out.println("2 aware beanName = " + name);
}
public void afterPropertiesSet() {
System.out.println("4 init");
}
public void destroy() {
System.out.println("6 destroy");
}
}
@Component
class LifeBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LifeService) {
System.out.println("3 before init");
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LifeService) {
System.out.println("5 after init");
}
return bean;
}
}常见误区
| 误区 | 正确理解 |
|---|---|
| 构造方法里依赖都可用 | 构造阶段属性还没填充完 |
| 初始化后一定是原对象 | 后置处理器可能返回代理对象 |
| prototype Bean 容器会自动销毁 | prototype 创建后交给调用方,Spring 不统一销毁 |
| BeanPostProcessor 只是回调 | AOP、事务代理、异步、缓存都依赖它 |
详细知识点:Bean生命周期。
阶段 5:依赖注入和自动装配
注入方式怎么选
| 注入方式 | 优点 | 风险 |
|---|---|---|
| 构造器注入 | 依赖清晰、便于测试、可用 final | 依赖太多会暴露类职责过重 |
| Setter 注入 | 适合可选依赖 | 对象可能处于半初始化状态 |
| 字段注入 | 写法短 | 隐藏依赖、测试困难、不利于不可变对象 |
业务 Service 推荐构造器注入:
import org.springframework.stereotype.Service;
@Service
public class AssetService {
private final AssetRepository assetRepository;
private final AuditService auditService;
public AssetService(AssetRepository assetRepository, AuditService auditService) {
this.assetRepository = assetRepository;
this.auditService = auditService;
}
}@Autowired 和 @Resource
| 注解 | 来源 | 默认规则 |
|---|---|---|
@Autowired | Spring | 按类型,多个候选时配合 @Qualifier、@Primary |
@Resource | JSR-250 | 默认按名称,再按类型 |
多个实现时必须明确选择:
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class NotifyService {
private final MessageSender messageSender;
public NotifyService(@Qualifier("smsSender") MessageSender messageSender) {
this.messageSender = messageSender;
}
}不明确会出现 NoUniqueBeanDefinitionException,生产启动直接失败。
详细知识点:@Autowired和@Resource区别。
阶段 6:循环依赖和三级缓存
什么是循环依赖
A 依赖 B,B 又依赖 A,就是循环依赖。
flowchart TD
A["ServiceA"] --> B["ServiceB"]
B --> ASpring 只能解决部分循环依赖:单例、非构造器注入、没有特别复杂代理链路的场景。构造器循环依赖通常解决不了,因为 A 构造时必须拿到 B,B 构造时又必须拿到 A,没有任何一方能先创建出对象壳子。
三级缓存简化流程
flowchart TD
A["创建 A 实例"] --> B["提前暴露 A 的 ObjectFactory"]
B --> C["填充 A 属性时需要 B"]
C --> D["创建 B 实例"]
D --> E["填充 B 属性时需要 A"]
E --> F["从三级缓存拿 A 的早期引用"]
F --> G["B 创建完成"]
G --> H["A 继续完成初始化"]为什么不建议依赖循环依赖
循环依赖往往说明职责边界混乱。比如采集服务依赖资产服务,资产服务又依赖采集服务,通常应该抽出编排服务、领域事件或独立查询服务。
详细知识点:循环依赖。
阶段 7:AOP 代理全过程
AOP 解决什么问题
AOP 用来处理横切逻辑:事务、日志、权限、审计、监控、缓存、异步等。这些逻辑不属于核心业务,但很多方法都需要。
调用链路
flowchart TD
A["外部调用"] --> B["代理对象"]
B --> C["拦截器链"]
C --> D["事务或日志等增强"]
D --> E["目标方法"]
E --> F["返回或抛异常"]
F --> G["增强收尾"]AOP Demo
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class CostAspect {
@Around("execution(* com.example..*Service.*(..))")
public Object cost(ProceedingJoinPoint point) throws Throwable {
long start = System.currentTimeMillis();
try {
return point.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
System.out.println(point.getSignature().getName() + " cost " + cost + "ms");
}
}
}JDK 动态代理和 CGLIB
| 代理方式 | 原理 | 限制 |
|---|---|---|
| JDK 动态代理 | 基于接口生成代理类 | 目标类需要接口 |
| CGLIB | 生成目标类子类 | final 类或 final 方法无法增强 |
自调用为什么失效
@Service
public class OrderService {
public void outer() {
this.inner(); // 绕过代理
}
@Transactional
public void inner() {
// 事务可能不生效
}
}this.inner() 调的是目标对象自己,不是代理对象,所以事务拦截器没有机会执行。
详细知识点:Spring AOP。
阶段 8:声明式事务和编程式事务
声明式事务是什么
声明式事务最常见写法是 @Transactional。它底层依赖 AOP 代理和 TransactionInterceptor。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderAppService {
private final OrderRepository orderRepository;
private final StockRepository stockRepository;
public OrderAppService(OrderRepository orderRepository, StockRepository stockRepository) {
this.orderRepository = orderRepository;
this.stockRepository = stockRepository;
}
@Transactional
public void createOrder(CreateOrderCommand command) {
orderRepository.insert(command.toOrder());
stockRepository.deduct(command.getSkuId(), command.getCount());
}
}声明式事务流程
flowchart TD
A["调用代理方法"] --> B["TransactionInterceptor"]
B --> C["读取事务注解"]
C --> D["选择事务管理器"]
D --> E["获取或创建事务"]
E --> F["绑定连接到当前线程"]
F --> G["执行业务方法"]
G --> H{"是否异常或标记回滚"}
H -->|否| I["提交事务"]
H -->|是| J["回滚事务"]
I --> K["清理线程资源"]
J --> K编程式事务是什么
编程式事务是在代码里主动控制事务边界,常用 TransactionTemplate。
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class ImportService {
private final TransactionTemplate transactionTemplate;
private final AssetRepository assetRepository;
public ImportService(TransactionTemplate transactionTemplate, AssetRepository assetRepository) {
this.transactionTemplate = transactionTemplate;
this.assetRepository = assetRepository;
}
public void importBatch(AssetBatch batch) {
transactionTemplate.executeWithoutResult(status -> {
try {
assetRepository.batchInsert(batch.getItems());
assetRepository.updateBatchStatus(batch.getBatchNo(), "SUCCESS");
} catch (RuntimeException ex) {
status.setRollbackOnly();
throw ex;
}
});
}
}声明式和编程式怎么选
| 场景 | 推荐 |
|---|---|
| 普通 Service 写操作 | 声明式事务 |
| 批量导入每 1000 条提交一次 | 编程式事务 |
| 方法内只有一小段需要事务 | 编程式事务 |
| 捕获异常后返回失败结果但仍要回滚 | 编程式事务或手动标记回滚 |
| 失败日志要独立提交 | REQUIRES_NEW 或独立事务 |
事务为什么会失效
| 原因 | 为什么 |
|---|---|
| 同类自调用 | 没经过代理对象 |
| 方法不是 public | 代理无法按预期增强 |
| 异常被 catch | 代理看到方法正常返回 |
| checked exception 未配置 rollbackFor | 默认不回滚受检异常 |
| 跨线程执行 | 事务资源绑定当前线程 |
| 数据库不支持事务 | 比如 MyISAM |
| 事务管理器选错 | 多数据源场景常见 |
详细知识点:Spring事务。
阶段 9:事务传播、连接绑定和 MyBatis 加入事务
传播行为解决什么问题
传播行为解决“一个事务方法调用另一个事务方法时,内层应该加入外层、新开事务、挂起事务还是非事务执行”。
| 传播行为 | 含义 | 常见场景 |
|---|---|---|
REQUIRED | 有事务就加入,没有就新建 | 默认,大多数业务 |
REQUIRES_NEW | 挂起外层,新开独立事务 | 失败日志、补偿记录 |
NESTED | 基于保存点的嵌套事务 | 局部回滚但依附外层 |
SUPPORTS | 有事务就加入,没有就非事务 | 查询类方法 |
NOT_SUPPORTED | 挂起事务,以非事务执行 | 不希望占用事务资源的慢操作 |
MyBatis 为什么能加入 Spring 事务
Spring 开启事务时,DataSourceTransactionManager 会从数据源获取连接,并把连接绑定到当前线程。MyBatis 通过 Spring 集成获取连接时,会先看当前线程是否已有绑定连接,如果有就复用。因此多个 Mapper 操作可以落在同一个物理事务里。
flowchart TD
A["事务代理开启事务"] --> B["DataSource 获取 Connection"]
B --> C["Connection 绑定到 ThreadLocal"]
C --> D["Mapper A 执行 SQL"]
C --> E["Mapper B 执行 SQL"]
D --> F["复用同一 Connection"]
E --> F
F --> G["统一 commit 或 rollback"]为什么事务内不建议调远程接口
远程接口慢会让数据库连接和锁长时间不释放;远程调用成功但本地事务回滚,会导致跨系统不一致。商业项目更推荐本地短事务 + 状态机 + 可靠消息 + 补偿任务。
详细知识点:Spring事务商业场景训练营。
阶段 10:事件机制和解耦
是什么
Spring 事件用于应用内部解耦。发布方只发布事件,不直接依赖所有处理方。
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
class OrderPaidEvent {
private final String orderNo;
OrderPaidEvent(String orderNo) {
this.orderNo = orderNo;
}
public String getOrderNo() {
return orderNo;
}
}
@Service
class OrderPayService {
private final ApplicationEventPublisher publisher;
OrderPayService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void paySuccess(String orderNo) {
publisher.publishEvent(new OrderPaidEvent(orderNo));
}
}
@Component
class CouponListener {
@EventListener
public void onOrderPaid(OrderPaidEvent event) {
System.out.println("send coupon for " + event.getOrderNo());
}
}注意边界
Spring 事件默认仍在应用内存里,不等于可靠消息。应用宕机、监听器失败、事务提交后回调失败,都可能导致事件丢失。核心跨系统链路要用 MQ、本地消息表、事务消息或 CDC。
阶段 11:扩展点怎么选
扩展点要按生命周期阶段选择。
| 扩展点 | 阶段 | 适合场景 |
|---|---|---|
BeanDefinitionRegistryPostProcessor | BeanDefinition 注册阶段 | 扫描接口并注册代理,如 Mapper、Feign |
BeanFactoryPostProcessor | Bean 实例化前 | 修改 BeanDefinition 属性 |
BeanPostProcessor | Bean 初始化前后 | 生成代理、包装 Bean |
FactoryBean | 特殊 Bean 创建 | 暴露代理对象或复杂对象 |
ImportSelector | 配置导入 | 根据注解决定导入哪些配置 |
DeferredImportSelector | 延迟导入 | Spring Boot 自动配置常用思想 |
ImportBeanDefinitionRegistrar | 编程式注册定义 | @EnableXxx 扫描注册组件 |
ApplicationListener | 事件监听 | 应用内事件解耦 |
扩展点选错会怎样
| 错误用法 | 后果 |
|---|---|
BeanFactoryPostProcessor 里 getBean() | 可能导致 Bean 过早创建,绕过后置处理器 |
| BeanPostProcessor 写复杂业务 | 容器启动变慢,所有 Bean 都受影响 |
| FactoryBean 里做远程慢调用 | getBean() 卡住,启动和运行都可能慢 |
| 事件当可靠消息 | 应用崩溃或回调失败会丢事件 |
详细知识点:Spring核心扩展点。
阶段 12:商业场景验收
场景 1:订单创建和库存扣减
要求:
- Controller 只做参数接收和校验。
- Service 作为事务边界。
- Repository/Mapper 只负责数据库。
- 订单创建和库存扣减在同一事务内。
- 支付通知、发券、积分不要全部塞进一个数据库事务。
验收问题:
- 为什么事务放在 Service 层?
- 为什么事务内不建议调远程支付接口?
- 如果库存扣减异常被 catch,事务会怎样?
场景 2:医疗数据采集批次入库
要求:
- 批次状态和明细入库一致。
- 大批量数据分批提交。
- 单批失败记录失败原因。
- 失败日志可以独立事务提交。
- 重试任务要幂等。
验收问题:
- 为什么一个超大事务风险高?
- 为什么
REQUIRES_NEW会增加连接池压力? - 编程式事务如何精确控制一批数据?
场景 3:框架集成
要求:
- MyBatis Mapper 接口可以被注入。
- Feign Client 可以像本地 Bean 一样调用。
- Spring Boot Starter 引入后能自动注册默认 Bean。
验收问题:
- Mapper 接口没有实现类,为什么能注入?
- Feign Client 为什么实际是代理对象?
- 自动配置最终是不是还要进入 Spring Bean 创建流程?
阶段 13:生产排查
注入失败排查
flowchart TD
A["启动注入失败"] --> B["Bean 是否被扫描"]
B --> C["是否有多个候选 Bean"]
C --> D["是否缺少 Qualifier 或 Primary"]
D --> E["是否条件注册没生效"]
E --> F["是否构造器循环依赖"]代理或事务失效排查
flowchart TD
A["事务或切面没生效"] --> B["调用是否经过 Spring Bean"]
B --> C["是否同类自调用"]
C --> D["方法是否 public"]
D --> E["异常是否抛到代理层"]
E --> F["是否跨线程"]
F --> G["数据库和事务管理器是否正确"]Bean 创建慢排查
常见原因:
- 构造方法或
@PostConstruct里调远程接口。 - BeanPostProcessor 对所有 Bean 做重逻辑。
- 初始化阶段加载大文件。
- 数据源、Redis、MQ 客户端连接超时。
- 循环依赖或条件配置导致反复尝试。
排查方法:
- 看启动日志卡在哪个 Bean。
- 打开 Spring 相关 debug 日志。
- 配合 Spring Boot Actuator startup、beans、conditions。
- 临时注释可疑初始化逻辑定位。
阶段 14:面试闭环
flowchart TD
A["面试题"] --> B["标准回答"]
B --> C["知识点页讲原理"]
C --> D["Demo 证明"]
D --> E["商业场景落地"]
E --> F["失败边界和排查"]| 面试题 | 必须答到的深度 |
|---|---|
| Spring 核心是什么 | IOC、AOP、BeanDefinition、生命周期、扩展点、代理 |
| IOC 是什么 | 控制反转、容器创建对象、依赖注入、生命周期管理 |
| Bean 生命周期 | 实例化、注入、Aware、BPP、初始化、代理、销毁 |
| 循环依赖 | 单例、三级缓存、早期引用、构造器循环不能解决 |
| AOP 原理 | 代理对象、拦截器链、JDK/CGLIB、自调用失效 |
| 声明式事务 | AOP、TransactionInterceptor、连接绑定、提交回滚 |
| 编程式事务 | TransactionTemplate、精确边界、分批提交、手动回滚 |
| 事务传播 | REQUIRED、REQUIRES_NEW、NESTED、连接池风险 |
| 事务失效 | 自调用、非 public、异常吞掉、跨线程、数据库引擎 |
| 扩展点 | BDRPP、BFPP、BPP、FactoryBean、ImportSelector、事件 |
面试标准回答看:Spring 面试知识点。
最终验收题
- 为什么不要在业务代码里到处
new依赖对象? - BeanDefinition 是什么?为什么 Spring 不直接创建对象?
- BeanFactory 和 ApplicationContext 有什么区别?
- Bean 生命周期完整流程是什么?
- BeanPostProcessor 为什么能生成 AOP 代理?
- 构造器注入、Setter 注入、字段注入怎么选?
@Autowired和@Resource区别是什么?- 循环依赖是什么?三级缓存解决了什么?
- 为什么构造器循环依赖解决不了?
- AOP 调用链路是什么?
- JDK 动态代理和 CGLIB 区别是什么?
- 同类自调用为什么导致 AOP 和事务失效?
@Transactional底层执行流程是什么?- 声明式事务和编程式事务怎么选?
REQUIRES_NEW为什么可能耗尽连接池?- MyBatis Mapper 为什么能加入 Spring 事务?
afterCommit为什么不能替代可靠消息?- 事务内为什么不建议调远程接口?
- Spring 事件适合什么,不适合什么?
- BeanDefinitionRegistryPostProcessor 和 BeanPostProcessor 区别是什么?
- FactoryBean 和 BeanFactory 区别是什么?
- ImportSelector 和 ImportBeanDefinitionRegistrar 适合什么场景?
- 注入失败怎么排查?
- 事务不回滚怎么排查?
- Bean 创建慢怎么排查?
本章小结
Spring 不是一堆注解,而是一套对象管理和扩展机制。先有 BeanDefinition,再有 BeanFactory 创建对象,再通过依赖注入组装对象,再通过生命周期扩展点介入,再用 AOP 代理实现事务和横切逻辑。学懂这条链,Spring Boot 自动配置、MyBatis Mapper、Feign Client、事务失效、循环依赖、扩展点选型都会连起来。
