0%

Spring循环依赖(下)

Spring循环依赖(下)

​ 上文中把springbean的创建过程大致整理了一下,但是似乎还没有回答上文背景中提出的问题,为什么在某个方法上加了一个@Async注解之后,导致出现了循环依赖报错,而上文中有两个简单小例子,其中使用了AOP,却没有出现循环依赖报错?(如果对spring中的AOP@Async实现方式有一个大概了解的话,应该知道二者都是通过生成代理对象的方式实现。)

这里举两个个例子:

  1. A对象和B对象相互依赖,且A对象中的printHelloWorld标记@Async
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class A {

@Autowired
private B b;

@Async
public void printHelloWorld(){

}
}

@Service
public class B {

@Autowired
private A a;
}


  1. A对象和B对象相互依赖,且A对象中的printHelloWorldAOP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Service
public class A {

@Autowired
private B b;

public void printHelloWorld(){

}
}

@Service
public class B {

@Autowired
private A a;
}

@Slf4j
@Aspect
@Component
public class LogAspect {

@Pointcut("execution(public * cn.com.xiaocainiaoya.cyclic.A.printHelloWorld(..))")
private void testPointcut() {}

@Around("testPointcut()")
public void around(ProceedingJoinPoint point) throws Throwable {
log.info("LogAspect before");
point.proceed();
log.info("LogAspect after");
}
}

​ 在第一个例子中,在创建B对象时,B通过第三级缓存中的提前暴露对象工厂获得的A对象是A在创建过程中的空对象。当A的经过初始化之后(在initialization()方法中会执行实现了BeanPostProcessor后置处理器中的postProcessAfterInitialization方法,而@Async的实现就是AsyncAnnotationBeanPostProcessor,经过该方法后生成的代理对象为proxyA),所以就导致A对象提前暴露给B对象注入的对象与最后初始化生成的对象不一致,根据上文说的判定逻辑,最终导致循环依赖报错。

循环依赖2.png

​ 在第二个例子中,在创建B对象时,B通过第三级缓存中的提前暴露对象工厂获取的A对象是经过BeanPostProcessor后置处理器的一个子扩展接口SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference方法,处理之后变为代理对象proxyA,(这里通过提前暴露对象的工厂获取到的就是代理对象,也就是提前暴露的对象就是代理对象),所以进入上文的判定逻辑,正常返回。

循环依赖2-1.png

注意:二者之前的差别就是提前暴露的对象是不是代理对象。获取说是代理对象的暴露时机有所不同。那么为什么二者暴露代理对象的时机有所不同?二者都是通过BeanPostProcessor的实现进行处理,AOP实现类和@Async的实现类AnnotationAwareAspectJAutoProxyCreatorAsyncAnnotationBeanPostProcessor有一个很大的区别是前者实现了SmartInstantiationAwareBeanPostProcessor接口,在提前暴露对象工厂中,只会执行实现了这个接口的getEarlyBeanReference方法来获取提前暴露对象。

1
2
3
4
5
6
7
8
9
10
11
12
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}

关键区别

1. 代理对象的作用范围

  • AsyncAnnotationBeanPostProcessor:
    • 它为标注了 @Async 的方法创建代理对象,仅拦截这些方法的调用。
    • 代理的目标是实现异步执行,与整个 bean 的生命周期管理无关。
    • 代理对象的创建发生在 bean 初始化后,仅用于增强方法行为,不影响依赖注入。
  • AbstractAutoProxyCreator:
    • 它的代理通常覆盖整个 bean,拦截所有方法调用。
    • 它的目的是实现 AOP 功能(如事务管理、权限验证等),可能影响依赖注入。
    • 代理对象需要提前暴露,以确保循环依赖场景下,其他 bean 引用的是代理对象。

2. 代理创建时机

  • AsyncAnnotationBeanPostProcessor:
    • 创建代理发生在 postProcessAfterInitialization 阶段。此时,bean 已经完成了依赖注入和初始化。
    • 不涉及循环依赖,因为它仅修改方法调用,不影响 bean 的结构或生命周期。
  • AbstractAutoProxyCreator:
    • 代理创建发生在 postProcessBeforeInstantiationgetEarlyBeanReference 阶段,以确保在循环依赖场景下,能够提前暴露代理对象。
    • 需要通过 SmartInstantiationAwareBeanPostProcessor 来参与三级缓存的管理,确保依赖注入过程中代理对象被正确使用。

3. 是否需要支持循环依赖

  • AsyncAnnotationBeanPostProcessor:
    • 不需要支持循环依赖。异步方法的功能是在运行时拦截,而不是在依赖注入阶段。
    • 因此,它无需实现 SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference
  • AbstractAutoProxyCreator:
    • 必须支持循环依赖,确保代理对象在注入过程中被正确引用。
    • 如果不实现 SmartInstantiationAwareBeanPostProcessor,在循环依赖场景中,注入的对象将是原始对象而非代理对象,从而导致 AOP 功能失效。

4. 功能的复杂性和设计目标

  • AsyncAnnotationBeanPostProcessor:
    • 其设计目标是简单、专注于异步方法的增强。它不需要处理复杂的依赖注入逻辑,也不需要参与 Spring 的三级缓存机制。
    • 通过后处理阶段(postProcessAfterInitialization)创建代理,已经足够满足其功能需求。
  • AbstractAutoProxyCreator:
    • 它是 Spring AOP 的核心组件,必须适应各种复杂场景,包括循环依赖、动态代理、BeanFactory 后处理等。
    • 实现 SmartInstantiationAwareBeanPostProcessor 是为了更通用地处理代理对象的提前暴露问题。

为什么设计如此

AsyncAnnotationBeanPostProcessor 的设计默认假设使用异步方法的场景通常不会涉及循环依赖。这种假设在大多数实际应用中是成立的:

  • 通常,@Async 的方法会在较独立的组件中定义,而这些组件一般不会与其他 bean 存在复杂的相互依赖关系。
  • 异步方法的调用者和被调用者通常是解耦的,调用者通过 Spring 的线程池机制执行异步任务,而不是直接依赖被调用者。

3. 如何解决循环依赖问题

如果在使用 @Async 时确实遇到循环依赖的问题,有几种解决方法:

方法 1: 通过重构解除循环依赖

  • 重新设计 bean 的依赖关系,减少相互依赖。
  • @Async 方法抽取到一个独立的组件中,确保其与其他依赖分离。

方法 2: 手动提前暴露代理对象

  • 使用 @Lazy 注解延迟加载其中一个依赖,避免循环依赖。
  • 或者在某些特殊情况下,可以通过自定义 BeanPostProcessor 提前创建代理对象,但这种做法会增加复杂性。

方法 3: 使用其他异步实现

  • 如果循环依赖问题无法避免,可以考虑使用其他异步调用机制(如直接使用 ExecutorService),避免 @Async 带来的代理问题。

方法 4: 引入显式的分层设计

  • 将异步逻辑和依赖逻辑分离到不同的服务层,减少交叉依赖。
-------------本文结束感谢您的阅读-------------