依赖注入、三级缓存与循环依赖
“能注入”“拿到代理”“已初始化完成”是三个不同命题。本篇从 A→B→A 的回查窗口推导引用身份,基线 Framework v6.1.12 / Boot v3.3.3。构造、填充、初始化与清理阶段以生命周期篇为准;这里不重复 refresh 流水线,也不把循环引用能力当作业务设计建议。
先选候选,再讨论创建窗口
DependencyDescriptor 描述注入点需要的类型、泛型、注解、是否必需。DefaultListableBeanFactory.resolveDependency/doResolveDependency 对单值依赖筛类型候选,再考虑 qualifier、primary、优先级和名称;数组、集合、Map、Optional、ObjectProvider 则走相应分支。固定源码入口。
Qualifier 是缩小候选集合,不是一概按名字查;泛型也能约束候选。两个同类型候选并不按扫描偶然顺序稳定选中,多个 Primary 仍可能冲突。Provider 延迟解析或获取,却不消除歧义。先记录类型、泛型、限定符、候选来源与父子容器范围:候选歧义与循环创建不是一种故障。歧义/缺依赖的可启动失败例、primary/qualifier/泛型/名称兜底的实测矩阵见配置与候选解析。
构造器注入声明必要依赖、便于建立不变量,但构造前就要拿到参数。setter/字段在实例存在后的属性填充窗口解析。这一时间差决定有没有 rawA 可以提前暴露;修改为 setter 改的是创建图的时序,不会自动改善职责图。
三级缓存是按需引用协议
| 结构 | 保存什么 | 不能推出什么 |
|---|---|---|
| singletonObjects | 已完成创建并发布的共享对象,可能为代理 | 业务字段天然线程安全 |
| earlySingletonObjects | 已实际取得的早期引用 | target 已完成属性填充与初始化 |
| singletonFactories | 生成早期引用的 ObjectFactory | 工厂里已经存好第三份代理实例 |
| singletonsCurrentlyInCreation | 创建中状态 | 只要状态为真就能解决所有环 |
三级工厂捕获已经存在的 rawA,延迟的是“对外暴露什么引用”的决定,不是重构造 A;它也不是 FactoryBean。无环时工厂可能根本不被调用,随后完成对象直接发布,不能说每个对象必须逐级晋升。
保留原文连续原码:DefaultSingletonBeanRegistry.getSingleton,v6.1.12,180–204 行。来源。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}先查一级,只有创建中才回查二级;允许早期引用且仍未命中才进同步区,再查一次,最后调用工厂并将结果移入二级。重查避免竞争下重复产出不同引用。一级的并发结构不表示对象图无锁创建,不能将其他版本锁策略拼到 6.1.12。
allowEarlyReference=false 仍能读一级和已有二级,只是不为这次读取调用三级工厂。完成发布时 addSingleton 清掉二/三级临时记录。理论上可以设计别的两级协议,但要另外表达延迟包装及状态;“必须恰好三张 Map”不是数学定理。
早期暴露的门槛与最终身份
A/B 都是单例,允许循环引用,从 A 的 setter 环开始:rawA 已存在 → 登记 factoryA → 填充 A 要 B → rawB 填充时回查 A → factoryA 产出 earlyA → B 持有 earlyA 并完成 → A 完成初始化 → 协调最终身份。若没有回查,earlyA 就不存在。
原码:AbstractAutowireCapableBeanFactory.doCreateBean,v6.1.12,584–636 行。实例构造位于本段之前,最终登记销毁和 return 位于之后。来源。
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
throw bce;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}guard 必须同时满足 singleton、allowCircularReferences、currentlyCreating;登记的是 lambda 工厂,不是当场无条件创建代理。populate/initialize 完成后 getSingleton(false) 只读取已经存在的早期引用。若 exposedObject 与原 bean 是同一身份,才改用 early 引用;不是 equals 值比较。
另一支处理初始化已经返回了不同对象的情况:禁止 raw 注入、存在真实依赖者,排除仅为类型检查创建的依赖者后,可能抛出身份冲突。不能简写成“创建了代理就报错”,也不能把这个片段误认作最终写一级缓存的代码。保住的不变量是 B.a == getBean("a"),而非“两个引用看上去都像代理”。
自动代理创建器只记原始身份
标准自动代理路径通过 SmartInstantiationAwareBeanPostProcessor 参与早期引用。下面两段均来自 AbstractAutoProxyCreator v6.1.12,分别为265–270 行与314–324 行,没有拼成一段连续原码。
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyBeanReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}map 存的是传入的原始 bean,用作身份标记;返回值才是可能包装后的引用。无匹配 advice、跳过或基础设施类仍可能返回原对象,所以“第三缓存存代理”“这个 map 存早期代理”都错。实际 early 引用归单例注册器二级缓存管理。
@Override
@Nullable
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyBeanReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}后置阶段 remove 同时取出并删除标记。与当前 bean 同一身份时直接返回当前 bean,不再包一次;它没有从 map 取出早期代理返回。最终用 early 代理替代 raw 是上一节 doCreateBean 的协调责任。删掉那支,不能说本方法独自保证身份。
若别的 BPP 改变了身份或没有早期标记,仍可能走 wrapIfNecessary;任意自定义包装组合并不保证只出现一层代理。代理选择和运行链回到AOP 正文,不要在两篇各维护一份代理算法。
失败矩阵:构造器、原型与晚包装
| 改变的条件 | 缺少的前提 / 可观察结果 | 正确处理方向 |
|---|---|---|
| A/B 都构造器互引 | rawA 尚未构造,无 factoryA 可交付 | 消除环,不能期待缓存创造未存在实例 |
| A 构造器要 B,B setter 要 A | 从 A 开始仍无 rawA;起点改变可能改变路径 | 画完整时序,不背“有 setter 就能解” |
| A/B 都 prototype 属性环 | 不满足 singleton guard;首次 getBean 才触发环失败 | 改依赖图,不把 refresh 成功当作反证 |
| 普通 BPP 只在 after 包 A | B 已持 rawA,最终成为 wrappedA | 修复早期协议或消环,而非关闭身份检查 |
创建失败会沿 AbstractBeanFactory 等失败路径清理早期结构、创建状态与关联依赖,防止再取失败半成品;这些集合清理无法撤回构造器泄漏到外部线程的 this。allowRawInjectionDespiteWrapping 放开可能让启动继续,但依赖方仍会绕过增强,不能把“无异常”当修复。
Framework 与 Boot 的开关不是同一默认值
Framework 6.1.12 工厂字段 allowCircularReferences 默认 true;Boot 3.3.3 SpringApplication 公开 setter 文档默认 false,在 prepareContext 写入工厂。
spring.main.allow-circular-references=true 只开放有限早期引用能力,不扩大到构造器和 prototype,也不替所有第三方 BPP 修复身份协议。主开关须足够早进入环境;等正在创建的业务 Bean 才提供配置可能已迟。实验显式设置工厂开关,不声称用纯 Framework 实验测到了 Boot 默认行为。
早期代理不是已就绪对象
即使 B.a 与最终代理完全相同,代理内部仍指向 rawA。在 B.init 调用 A.work 时,A.b 可能还没填充,方法会读到半成品状态。三级协议闭合的是引用图,不是业务就绪状态。此变式属于纸面推演,本实验不在 init 互调业务。
排障依次核对注入形状、起点、scope、开关、实际代理处理器,再直接比较引用身份。IdentityHashCode 适合日志辅助,验证时用 ==,不要只比较代理类名。引用安全发布也不等于对象上每个业务动作在启动中都安全。
实验:五种输入只改一个关键条件
执行 JAVA_HOME=$(/usr/libexec/java_home -v17) bash labs/spring-senior/run.sh circle,完整环境见实验说明。每个模式使用独立 Context 并 finally 关闭。
| 输入 | 断言的预期 | 本批实测 |
|---|---|---|
| setter | 刷新成功,B.a 与最终 A 是同一代理,ping 增加一次拦截 | 通过,intercepts=1 |
| constructor | 异常链包含 BeanCurrentlyInCreationException | 通过 |
| forbidden | 同属性环仅关闭支持即失败 | 通过 |
| late-wrapper | 创建环异常且原因是 raw/最终包装冲突 | 通过 |
| prototype | refresh 不预创建,首次 getBean("a") 失败 | 通过 |
断言异常链中的语义而非最外层错误文本,避免把任意 BeansException 当成功。公开 Java 源码如下,运行的就是这一个文件。
package study.spring;
import java.util.concurrent.atomic.AtomicInteger;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.*;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.support.*;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.*;
import org.springframework.context.support.GenericApplicationContext;
public class CircleLab {
public static class A {B b;public void setB(B value){b=value;}public void ping(){}}
public static class B {A a;public void setA(A value){a=value;}}
public static class CA {public CA(CB b){}}
public static class CB {public CB(CA a){}}
public static void main(String[] args){
String mode=args.length==0?"setter":args[0];
if(!java.util.List.of("setter","constructor","forbidden","late-wrapper","prototype").contains(mode))throw new IllegalArgumentException(mode);
var c=new GenericApplicationContext();
c.getDefaultListableBeanFactory().setAllowCircularReferences(!mode.equals("forbidden"));
if(mode.equals("constructor")){
c.registerBeanDefinition("a",BeanDefinitionBuilder.genericBeanDefinition(CA.class).setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition());
c.registerBeanDefinition("b",BeanDefinitionBuilder.genericBeanDefinition(CB.class).setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition());
}else{
String scope=mode.equals("prototype")?"prototype":"singleton";
c.registerBeanDefinition("a",BeanDefinitionBuilder.genericBeanDefinition(A.class).setScope(scope).addPropertyReference("b","b").getBeanDefinition());
c.registerBeanDefinition("b",BeanDefinitionBuilder.genericBeanDefinition(B.class).setScope(scope).addPropertyReference("a","a").getBeanDefinition());
}
var count=new AtomicInteger();
if(mode.equals("late-wrapper"))c.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor(){
public Object postProcessAfterInitialization(Object bean,String name){if(!name.equals("a"))return bean;var p=new ProxyFactory(bean);p.setProxyTargetClass(true);p.addAdvice((MethodInterceptor)inv->inv.proceed());return p.getProxy();}
});
else if(mode.equals("setter")){
c.registerBean("autoProxyCreator",DefaultAdvisorAutoProxyCreator.class);
var pc=new StaticMethodMatcherPointcut(){public boolean matches(java.lang.reflect.Method m,Class<?> type){return type==A.class&&m.getName().equals("ping");}};
c.registerBean("advisor",DefaultPointcutAdvisor.class,()->new DefaultPointcutAdvisor(pc,(MethodInterceptor)inv->{count.incrementAndGet();return inv.proceed();}));
}
boolean failed=false;
try{
c.refresh();
// Prototypes are not pre-instantiated by refresh; lookup triggers this cycle.
if(mode.equals("prototype"))c.getBean("a");
if(!mode.equals("setter"))throw new AssertionError("expected failure: "+mode);
var a=c.getBean("a");var b=c.getBean(B.class);
if(a!=b.a||!AopUtils.isAopProxy(a))throw new AssertionError("early proxy identity");
b.a.ping();if(count.get()!=1)throw new AssertionError("advisor count");
}
catch(org.springframework.beans.BeansException e){
failed=true;if(mode.equals("setter"))throw e;
Throwable cause=e;
while(cause!=null&&!(cause instanceof org.springframework.beans.factory.BeanCurrentlyInCreationException))cause=cause.getCause();
if(cause==null)throw new AssertionError("not a creation-cycle failure",e);
if(mode.equals("late-wrapper")&&!cause.getMessage().contains("raw version"))throw new AssertionError("not a wrapping conflict",e);
}
finally{c.close();}
System.out.println("PASS circle "+mode+" failed="+failed+" intercepts="+count.get());
}
}未测 FactoryBean 环、任意第三方 BPP、并发提前调用与混合环起点组合;不要把五个模式推广成所有应用都可循环注入。
架构师推演:消除环而不偷换一致性
OrderService 需要 InventoryService 扣库存,后者又需要前者判断是否释放。候选一改 setter 并开环,改动少,但初始化窗口和职责互依继续存在。候选二提取 CheckoutCoordinator,让订单与库存提供独立操作,只依赖低层查询接口;调用由上层编排,测试边界更清晰。
同库且需原子操作时,编排可使用现有本地事务边界,具体语义回查事务正文。跨库、独立团队或扩容条件改变后才考虑事件与可恢复最终一致,同时支付 Outbox、幂等、补偿、状态查询和对账成本。拆依赖环不是采用分布式消息的充分理由。
Provider/Lazy 适合真正可选或延迟的依赖;在构造器里立即 getObject/调用代理,只是把问题换一个入口。连续追问可按“为何要延迟工厂 → 谁保证同一代理 → 为什么还会未就绪 → 是否应该消环”推进。验收应检查业务仍经过必要增强、初始化不互调、故障能恢复,而不是只看启动成功。
闭卷验收
本页 8 道闭卷题收进了下方自测面板:先回忆,再展开参考答案与评分点,最后给三档自评。