Spring IoC 容器与依赖注入全过程
IoC(控制反转)不是“Spring 帮你少写几个 new”,而是对象定义、创建时机、依赖选择、生命周期、作用域和增强权从业务代码转交给容器。DI(依赖注入)是实现 IoC 的主要方式:Bean 声明需要什么,容器解析候选并注入。Spring AOP、事务、事件和 Boot 自动配置都建立在这条容器主线上。
学习目标
- 区分 IoC、DI、BeanFactory、ApplicationContext 和单例池;
- 讲清配置类/注解/XML怎样转换为 BeanDefinition;
- 理解
ApplicationContext.refresh()的关键阶段和扩展点顺序; - 讲清
getBean()从名称解析、作用域、依赖图到创建对象的过程; - 理解构造器、Setter、字段注入分别由谁执行;
- 掌握
@Primary、@Qualifier、名称、泛型和ObjectProvider的候选选择; - 理解 singleton、prototype、request、session 和作用域代理;
- 能排查找不到 Bean、候选不唯一、循环依赖、过早实例化、启动慢和 Bean 被代理后的类型问题。
一、IoC 与 DI 是什么关系
没有容器:
public class OrderService {
private final PaymentClient paymentClient = new HttpPaymentClient();
}OrderService 决定:创建哪个实现、使用什么配置、何时创建、何时关闭。使用容器:
public class OrderService {
private final PaymentClient paymentClient;
public OrderService(PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}
}- IoC:对象控制权从业务类反转给容器;
- DI:容器把解析出的 PaymentClient 传入构造器;
- DIP:业务依赖抽象而非具体实现,是设计原则;
- Service Locator:业务主动向容器
getBean查依赖,不是推荐的常规 DI。
IoC 的核心价值是把对象图和生命周期集中管理,使替换实现、测试、配置、代理增强和资源关闭有统一入口。
二、Spring 容器不是一个 Map
flowchart TD
A["配置元数据"] --> B["BeanDefinition注册表"]
B --> C["BeanFactory创建引擎"]
C --> D["依赖解析与生命周期处理器"]
D --> E["单例对象和其他作用域对象"]
F["ApplicationContext"] --> C
F --> G["事件资源国际化环境等能力"]| 概念 | 主要职责 |
|---|---|
| BeanDefinition | 描述“怎样创建一个 Bean” |
| BeanDefinitionRegistry | 保存和注册定义 |
| BeanFactory | getBean、依赖解析、作用域和生命周期核心接口 |
| DefaultListableBeanFactory | 常见完整实现,兼具定义注册和候选解析 |
| ApplicationContext | 在 BeanFactory 上增加 refresh、事件、资源、环境、国际化等 |
| Singleton Registry | 保存已创建单例及创建中辅助状态 |
单例池只保存成品对象,是容器的一部分。若只有 Map<String,Object>,就无法在对象创建前修改定义、按作用域创建、处理 FactoryBean、执行后置处理器和生成 AOP 代理。
三、配置怎样变成 BeanDefinition
Spring 支持多种配置来源:
@Component扫描
@Bean方法
@Import
XML <bean>
编程式 registerBeanDefinition
框架 Registrar/自动配置无论来源,核心目标都是向 BeanFactory 注册 BeanDefinition:
flowchart TD
A["读取配置源"] --> B["解析类方法属性和条件"]
B --> C["确定Bean名称"]
C --> D["构建BeanDefinition"]
D --> E["注册到DefinitionRegistry"]
E --> F["后续按定义实例化"]3.1 BeanDefinition 保存什么
- Bean class 或 class name;
- 工厂 Bean、工厂方法;
- 构造参数和属性值;
- scope;
- lazy-init;
- primary、autowire candidate;
- depends-on;
- init/destroy method;
- role、description、source 等元数据。
它不是 Java 对象实例,而是创建配方。定义注册完成时,普通业务 Bean 可能尚未实例化。
3.2 @Component 扫描
组件扫描读取 Class 元数据,筛选 @Component 及组合注解,生成 ScannedGenericBeanDefinition。扫描路径过宽会增加启动 I/O 和候选数量;默认包下扫描整个 ClassPath 是常见错误。
3.3 @Bean 方法
@Configuration 类由 ConfigurationClassPostProcessor 解析,@Bean 方法注册为工厂方法定义。完整版 @Configuration(proxyBeanMethods=true) 常通过 CGLIB 拦截同配置类内方法调用,确保 singleton @Bean 返回容器对象;Lite 配置或 proxyBeanMethods=false 的直接 Java 调用可能新建普通对象。
@Configuration
public class PaymentConfig {
@Bean
public PaymentClient paymentClient() {
return new HttpPaymentClient();
}
}Spring 5.2 起 proxyBeanMethods=false 常用于不需要方法间调用保证的配置,减少增强成本;Spring Boot 自动配置大量采用 Lite 风格,但必须理解方法调用边界。
3.4 Bean 名称和覆盖
默认组件名通常由类名推导,@Bean 默认使用方法名。名称冲突是否允许覆盖取决于容器/Boot 配置和版本。依赖覆盖改变实现非常脆弱,应使用明确条件、@Primary 或限定符,而不是赌注册顺序。
四、ApplicationContext.refresh 主流程
refresh() 是 ApplicationContext 启动骨架。不同上下文细节不同,但经典阶段是:
flowchart TD
A["prepareRefresh"] --> B["obtainFreshBeanFactory"]
B --> C["prepareBeanFactory"]
C --> D["postProcessBeanFactory"]
D --> E["invokeBeanFactoryPostProcessors"]
E --> F["registerBeanPostProcessors"]
F --> G["初始化MessageSource和事件广播器"]
G --> H["onRefresh与注册Listener"]
H --> I["finishBeanFactoryInitialization"]
I --> J["finishRefresh发布完成事件"]4.1 prepareRefresh
设置启动时间、active 状态,初始化/校验 Environment 属性。此时业务单例通常还没有创建。
4.2 obtainFreshBeanFactory
取得或刷新底层 BeanFactory,并加载初始 BeanDefinition。XML 上下文会在这里读取 XML;注解上下文的基础定义可能已注册,后续配置类处理器继续展开。
4.3 prepareBeanFactory
设置类加载器、表达式解析器、属性编辑器,注册若干 Aware 处理器和可解析依赖,让 Bean 能感知 BeanFactory、ApplicationContext 等基础设施。
4.4 invokeBeanFactoryPostProcessors
先执行 BeanDefinitionRegistryPostProcessor,再执行 BeanFactoryPostProcessor。ConfigurationClassPostProcessor 在这里解析 @Configuration、@ComponentScan、@Import、@Bean,可能继续注册更多定义。
此阶段操作的是定义和工厂,不应过早调用普通 Bean;否则会在完整 BeanPostProcessor 注册前实例化,导致 AOP/注解处理缺失。
4.5 registerBeanPostProcessors
发现并排序 BeanPostProcessor,注册到 BeanFactory。AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、AOP 自动代理创建器等在 Bean 创建过程中发挥作用。
4.6 finishBeanFactoryInitialization
完成类型转换服务、占位符等准备后,调用 preInstantiateSingletons 创建非懒加载 singleton。此处通常是启动耗时和 BeanCreationException 集中暴露阶段。
4.7 finishRefresh
初始化生命周期处理器,发布 ContextRefreshedEvent,容器进入可用状态。事件监听器若执行慢远程调用会延迟启动完成。
五、getBean() 全过程
flowchart TD
A["调用getBean名称或类型"] --> B["转换别名和FactoryBean前缀"]
B --> C{"单例缓存有成品吗"}
C -- "有" --> D["取出对象"]
C -- "无" --> E["查找BeanDefinition"]
E --> F["合并父定义并校验"]
F --> G["处理dependsOn"]
G --> H["按scope选择创建策略"]
H --> I["createBean"]
I --> J["返回Bean或FactoryBean产品"]
D --> J5.1 名称、别名与 &
Bean 可有别名。若 Bean 是 FactoryBean:
context.getBean("clientFactory"); // 默认得到 getObject() 产品
context.getBean("&clientFactory"); // 得到 FactoryBean 本身BeanFactory 是容器接口,FactoryBean 是“生产特殊对象的 Bean”,两者名字相似但职责完全不同。
5.2 父容器查找
当前 BeanFactory 找不到时,某些层次上下文会委托父容器。父容器通常看不到子容器 Bean,子容器可查父容器。传统 Spring MVC 根上下文/DispatcherServlet 子上下文中会影响 Service、Controller 可见性。
5.3 dependsOn
@DependsOn/depends-on 控制创建先后和销毁反序,但不等于字段注入。它适用于必须先初始化驱动、注册中心等隐式依赖;滥用会隐藏真实依赖和拉长启动链。
六、createBean() 与生命周期边界
flowchart TD
A["resolveBeforeInstantiation"] --> B{"前置处理器直接返回代理吗"}
B -- "是" --> C["使用短路对象"]
B -- "否" --> D["createBeanInstance实例化"]
D --> E["提前暴露候选引用"]
E --> F["populateBean依赖注入"]
F --> G["initializeBean"]
G --> H["BeanPostProcessor可能返回代理"]
H --> I["注册销毁回调并放入作用域"]生命周期的逐个回调、Aware、@PostConstruct、InitializingBean、init-method、销毁顺序见 Bean 生命周期全过程。循环依赖三级缓存和早期代理见 循环依赖全过程。本页聚焦容器定义、创建和依赖解析,不重复两页全部内容。
七、实例化和依赖注入不是一回事
- 实例化:选择构造器/工厂方法并产生对象;
- 属性填充:给已实例化对象注入字段/Setter/属性;
- 初始化:执行 Aware、BPP、初始化回调,可能产生代理。
构造器注入发生在实例化阶段,因此构造器 A 需要 B、B 又需要 A 时,没有任何一个实例可以先产生,Spring 无法靠早期引用解决。字段/Setter 循环是对象先实例化后填充,部分 singleton 场景才有提前暴露的可能。
八、构造器怎样选择
常见规则受 Spring 版本影响,但主思路:
- 查找显式
@Autowired构造器; - 单构造器类通常可省略
@Autowired(Spring 4.3+); - 多候选时评估 required、参数可解析性和贪婪程度;
- 找不到可满足构造器则抛 BeanCreationException/UnsatisfiedDependencyException;
- 不应依赖模糊的“参数最多一定选中”。
@Service
public class OrderService {
private final InventoryRepository inventoryRepository;
private final PaymentClient paymentClient;
public OrderService(InventoryRepository inventoryRepository,
PaymentClient paymentClient) {
this.inventoryRepository = inventoryRepository;
this.paymentClient = paymentClient;
}
}构造器注入让对象创建后依赖完整、字段可 final、测试可直接 new。可选依赖不应通过大量 nullable 构造器制造歧义,可使用 Optional、ObjectProvider 或明确配置条件。
九、@Autowired 候选解析全过程
AutowiredAnnotationBeanPostProcessor 扫描构造器、字段和方法注入点,创建 DependencyDescriptor,并委托 BeanFactory 解析候选:
flowchart TD
A["发现注入点类型PaymentClient"] --> B["筛选类型可赋值Bean"]
B --> C["应用autowireCandidate和Qualifier"]
C --> D{"只剩一个吗"}
D -- "是" --> E["选择并获取Bean"]
D -- "否" --> F["检查Primary和优先级"]
F --> G["检查注入点名称匹配"]
G --> H{"仍唯一吗"}
H -- "是" --> E
H -- "否" --> I["抛NoUnique或无法解析"]9.1 多实现
public interface PaymentClient { }
@Component("wechatPaymentClient")
class WechatPaymentClient implements PaymentClient { }
@Component("bankPaymentClient")
class BankPaymentClient implements PaymentClient { }解决方式:
public OrderService(
@Qualifier("bankPaymentClient") PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}或给默认实现加 @Primary。Qualifier 表达业务语义比依赖字段名碰巧匹配 Bean 名更稳定;多个 @Primary 仍会冲突。
9.2 集合注入
public RuleEngine(List<ValidationRule> rules) {
this.rules = rules;
}Spring 注入所有 ValidationRule,可结合 @Order/Ordered 排序。Map 注入时 key 通常是 Bean 名。插件链中新增 Bean 会自动进入集合,因此必须定义顺序、重复规则和失败隔离。
9.3 泛型限定
Spring 的 ResolvableType 可利用泛型元数据区分候选,例如 Handler<Order> 与 Handler<User>。运行时类型擦除不表示框架完全看不到声明签名中的泛型元数据;但代理、桥接方法和原始类型会影响解析,需测试。
十、字段、Setter 和方法注入是谁完成的
字段注入不是 JVM 构造器能力,而是 BeanPostProcessor 在 populateBean 阶段通过反射设置字段。Setter/任意 @Autowired 方法也由相同处理器调用。
| 方式 | 优点 | 风险/适用 |
|---|---|---|
| 构造器 | 强制依赖、final、易测试 | 参数过多暴露类职责过重 |
| Setter | 可选依赖、显式可变 | 对象可能短暂不完整 |
| 字段 | 写法短 | 隐藏依赖、难直接测试、不能final |
生产 Service 优先构造器。框架基类或真正可选属性可使用 Setter。字段注入不是“绝对不能运行”,而是可维护性和可测试性更差。
十一、延迟和可选依赖
11.1 @Lazy
用在 Bean 定义上表示延迟实例化;用在注入点上常注入延迟代理,首次调用再解析目标。它能缩短启动或打破某些依赖时机,但会把错误推迟到首次流量,并不能修复职责耦合。
11.2 ObjectProvider
public ExportService(ObjectProvider<OptionalExporter> provider) {
this.provider = provider;
}
public void export() {
OptionalExporter exporter = provider.getIfAvailable();
if (exporter != null) exporter.export();
}ObjectProvider 支持按需获取、可选、迭代和有序流,避免业务到处保存 ApplicationContext 并调用 getBean。getObject() 找不到会抛异常,getIfAvailable() 才是可选语义。
11.3 Optional
注入 Optional<T> 可表达候选可能不存在,但滥用可选依赖会让核心能力静默缺失。关键依赖应启动失败,只有真正可选插件才降级。
十二、作用域全过程
| Scope | 创建/缓存位置 | 销毁管理 |
|---|---|---|
| singleton | 每个 BeanFactory 一份,不是 JVM 全局 | Context 关闭时回调 |
| prototype | 每次 getBean/解析通常新建 | 容器不完整管理销毁 |
| request | 每个 HTTP Request | 请求结束 |
| session | 每个 HttpSession | Session 销毁 |
| application | ServletContext 级 | 应用关闭 |
12.1 singleton 注入 prototype
构造 singleton 时解析一次 prototype,之后 singleton 一直持有同一个实例,不会每次方法调用都新建。需要每次获取时使用 ObjectProvider、lookup method 或作用域代理。
12.2 Web scope 注入 singleton
应用启动时没有具体 Request/Session。通常注入作用域代理:singleton 持有代理,方法调用时代理从当前 RequestContext 取真实对象。无请求线程调用会抛 ScopeNotActiveException/相关异常。
12.3 prototype 销毁
容器负责创建和初始化 prototype,但通常不自动跟踪其销毁。prototype 若持有文件、线程、连接,调用方要明确关闭;不要把短生命周期资源隐藏成 prototype 后期待容器停机统一清理。
十三、FactoryBean 全过程
FactoryBean 用于创建过程复杂、类型由框架决定或需要代理封装的对象:
public class ClientFactoryBean implements FactoryBean<ApiClient> {
public ApiClient getObject() {
return new ApiClient("https://api.example.com");
}
public Class<?> getObjectType() { return ApiClient.class; }
public boolean isSingleton() { return true; }
}容器先管理 FactoryBean 自身,再在普通 getBean(name) 时调用 getObject() 并按 isSingleton 缓存产品。&name 才取工厂。FactoryBean 产品类型预测影响按类型注入,getObjectType 返回 null 会让启动阶段候选发现更困难。
十四、BeanPostProcessor 为什么能实现注入与代理
- InstantiationAwareBeanPostProcessor:实例化前后、属性填充参与;
- AutowiredAnnotationBeanPostProcessor:处理
@Autowired、@Value; - CommonAnnotationBeanPostProcessor:常处理
@Resource、@PostConstruct等(版本/包体系相关); - AbstractAutoProxyCreator 子类:判断是否需要创建 AOP 代理;
- DestructionAwareBeanPostProcessor:销毁前参与。
如果 Bean 在 BeanPostProcessor 全部注册前被某个 BFPP 过早 getBean,它可能没有被自动代理或注解处理,日志常出现“not eligible for getting processed by all BeanPostProcessors”。BFPP 中应操作 BeanDefinition,不要随意依赖普通业务 Bean。
十五、完整可运行 Demo
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
public class IocDemo {
public interface PaymentClient {
String pay(String orderNo, BigDecimal amount);
}
public static class BankPaymentClient implements PaymentClient {
public String pay(String orderNo, BigDecimal amount) {
return "BANK:" + orderNo + ":" + amount;
}
}
public static class MockPaymentClient implements PaymentClient {
public String pay(String orderNo, BigDecimal amount) {
return "MOCK:" + orderNo;
}
}
public static class OrderService {
private final PaymentClient paymentClient;
public OrderService(
@Qualifier("bankPaymentClient") PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}
public String create(String orderNo, BigDecimal amount) {
return paymentClient.pay(orderNo, amount);
}
}
@Configuration
public static class Config {
@Bean
@Primary
public PaymentClient mockPaymentClient() {
return new MockPaymentClient();
}
@Bean
public PaymentClient bankPaymentClient() {
return new BankPaymentClient();
}
@Bean
public OrderService orderService(
@Qualifier("bankPaymentClient") PaymentClient client) {
return new OrderService(client);
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
try {
OrderService service = context.getBean(OrderService.class);
System.out.println(service.create(
"ORDER-1001", new BigDecimal("99.90")));
} finally {
context.close();
}
}
}Demo 同时验证:配置类解析、@Bean 定义注册、两个同类型候选、@Primary 默认候选、@Qualifier 精确选择、构造器注入、singleton 获取和 Context 关闭。
十六、商业场景:可插拔采集处理链
public interface DataNormalizer {
boolean supports(String sourceType);
NormalizedData normalize(RawData raw);
}
@Service
public class NormalizationService {
private final List<DataNormalizer> normalizers;
public NormalizationService(List<DataNormalizer> normalizers) {
this.normalizers = normalizers;
}
}新数据源只需新增 Bean,容器注入集合。必须进一步定义:@Order 顺序、是否允许多个 supports、无匹配时错误、单个插件异常是否隔离、插件是否线程安全。IoC 提供组装机制,不替业务制定规则。
十七、常见启动异常怎样读
17.1 NoSuchBeanDefinitionException
检查类型/Qualifier、扫描包、Profile/Condition、配置是否被 Import、Bean 是否在父/子上下文另一侧、是否使用了错误包名或版本。不要看到错误就全局扩大 ComponentScan。
17.2 NoUniqueBeanDefinitionException
错误会列出候选名称。确认业务应该选择一个、全部集合还是策略路由;使用 @Qualifier/@Primary,不要删除某个实现来让错误暂时消失。
17.3 UnsatisfiedDependencyException
它通常是外层包装,继续阅读最深 Caused by。真正原因可能是候选不存在、构造器抛异常、配置绑定失败、循环依赖或下游 Bean 初始化失败。
17.4 BeanCurrentlyInCreationException
常见循环依赖或 FactoryBean 创建递归。先画依赖图,优先拆职责/提取编排,而不是加 @Lazy 掩盖所有循环。
17.5 BeanNotOfRequiredTypeException
可能按名称取得了不同 Bean,或 JDK 代理只能按接口赋值、代码却要求具体实现类。检查实际 bean.getClass()、代理类型和注入点类型。
十八、启动慢生产排查
flowchart TD
A["应用启动慢"] --> B["记录refresh阶段与Bean创建耗时"]
B --> C{"慢在扫描定义还是实例化"}
C -- "扫描" --> D["缩小ComponentScan和条件扫描"]
C -- "实例化" --> E["定位具体Bean init构造器和BPP"]
E --> F{"是否远程IO或锁等待"}
F -- "是" --> G["设置超时移出启动或明确失败"]
F -- "否" --> H["分析代理反射类加载和大量Bean"]证据包括 Spring Startup 指标(版本支持时)、Actuator startup、Flight Recorder、线程栈、Condition Evaluation Report、DEBUG/TRACE 创建日志。不要一上来给所有 Bean 加 @Lazy:它会把错误和延迟移到首个请求,造成流量进入后抖动。
十九、生产设计与排查 Runbook
19.1 注入错实现
记录注入点声明类型、Qualifier、所有候选 Bean 名、Primary、Profile/Condition 和实际运行类型。检查配置覆盖与测试 Mock 是否进入生产;不要只看接口类名。
19.2 Bean 有时不是代理
检查是否手工 new、是否过早实例化、是否由不受 Spring 管理的工厂创建、注入的是目标还是代理、BeanPostProcessor 是否注册完整、AOP Pointcut 是否匹配。对比 bean.getClass() 和是否 AopUtils.isAopProxy。
19.3 Context 关闭资源没释放
检查 singleton 销毁回调、线程池/客户端 close、prototype 自己管理、Bean 是否从未成功初始化、进程是否被强杀。堆转储查旧 ApplicationContext/ClassLoader 引用链。
19.4 运行时临时从 Context 找 Bean
大量 applicationContext.getBean(type) 隐藏依赖、难测试并可能在运行时才暴露候选冲突。插件路由使用注入 Map<String,Strategy> 或 ObjectProvider;只有框架基础设施才合理使用容器查找。
二十、Spring 版本边界
| 版本 | 重要边界 |
|---|---|
| Spring 4.3 | 单构造器可省略 @Autowired |
| Spring 5.x | 常见 JDK 8 企业项目;5.3 是旧线常见末期版本 |
| Spring 6 | 基线 Java 17,迁移 javax.* 到 jakarta.* |
| Spring Boot 3 | 基于 Spring 6/Jakarta,不兼容只支持 javax 的旧组件 |
IoC 主原理连续,但注解包、CGLIB 内置方式、循环依赖默认策略和 AOT 会演进。阅读源码必须标注 Spring 具体版本,不能把 Spring 2/5/6 的实现细节混在一个回答中。
二十一、面试标准回答
IoC 和 DI 区别
IoC 是控制权从业务代码反转给容器,DI 是容器实现控制反转的主要手段:解析 BeanDefinition 和依赖候选后,通过构造器、方法或字段把对象注入。IoC 还包括作用域、生命周期、代理增强和资源管理,不只是依赖赋值。
BeanFactory 和 ApplicationContext 区别
BeanFactory 定义 Bean 获取、创建、依赖解析和作用域的核心能力;ApplicationContext 组合/扩展 BeanFactory,并提供 refresh 生命周期、事件、Environment、Resource、国际化等应用能力,通常启动时预实例化非懒 singleton。
BeanDefinition 为什么重要
它把对象实例与创建元数据分离。Spring 可在实例化前通过注册器和 BFPP 修改 class、scope、属性、条件和工厂方法,实例化后再用 BPP 注入和代理;自动配置、占位符、AOP 都依赖这条“先定义后对象”的链。
@Autowired 多个实现怎样选择
先按类型筛选,再应用 autowire candidate 和 Qualifier,之后考虑 Primary、优先级和注入点名称;仍不唯一抛 NoUniqueBeanDefinitionException。业务应使用语义明确的 Qualifier 或注入集合做策略路由。
singleton 是 JVM 单例吗
不是,Spring singleton 默认是每个 BeanFactory 对一个 Bean 名一份。创建多个 ApplicationContext 可以各有一份;同一实例会被多线程共享,所以 singleton Service 不应保存请求级可变状态。
为什么 singleton 注入 prototype 后没有每次新建
依赖在 singleton 创建时只解析一次,得到的 prototype 引用被长期保存。每次调用需要新对象时应注入 ObjectProvider、使用 lookup method 或作用域代理。
二十二、关联知识与验收
掌握验收:
- 能从配置源画到 BeanDefinition、refresh、preInstantiateSingletons 和 getBean;
- 能说出 refresh 至少八个关键阶段及阶段对象是定义还是实例;
- 能解释实例化、属性填充和初始化为什么不是同一步;
- 能按类型、Qualifier、Primary、名称和泛型分析候选选择;
- 能解释 singleton 注入 prototype 和 request 代理;
- 能区分 BeanFactory、FactoryBean、ApplicationContext 和单例池;
- 面对 NoSuch、NoUnique、Unsatisfied、CurrentlyInCreation,能沿最深 cause 和依赖图排查;
- 面对启动慢,能判断慢在扫描、定义处理还是具体 Bean/BPP,而不是全局加 Lazy。
