JAVA && Spring && SpringBoot2.x — 学习目录
说起循环依赖,大家可能多少有点陌生。
概念:创建Bean A时需要依赖Bean B,但是Bean B的创建又依赖Bean A。导致项目启动初始化Bean时,出现了一个环形的依赖关系。
循环依赖.png解决办法:spring使用三级缓存支持循环依赖。
使用场景:内部方法调用事务方法,为保证事务生效,我们使用循环依赖,将自己注入自己。
@Service
public class AccountService {
//循环依赖,保证内部方法调用事务方法时为代理对象
@Autowired
private AccountService accountService;
//将事务操作抽取为一个方法(缩小事务的作用范围)
@Transactional
public void ts() {
}
//外界调用的是该方法进行业务处理
public void noTransationTs() {
//业务逻辑
//实际上,是代理对象调用事务方法,事务会生效!
accountService.ts();
//业务逻辑
}
}
项目启动:居然抛出异常了!大概意思是:循环引用失败:最终暴露的Bean和属性依赖的Bean不同。
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'accountService':
Bean with name 'accountService' has been injected into other beans [accountService] 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.
等等,你不是说有三级缓存可以防止循环依赖吗?为啥会抛出异常!
2. Spring断点循环依赖分析
创建Bean的主流程,方便断点调试
//源码位置:org.springframework.context.support.AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
// 实例化剩余的非延迟加载的bean(实例化普通bean)
finishBeanFactoryInitialization(beanFactory);
}
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 实例化普通的bean对象
beanFactory.preInstantiateSingletons();
}
@Override
public void preInstantiateSingletons() throws BeansException {
//扫描容器得到的所有BeanDefinitionNames
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
//开始实例化使用非懒加载的bean
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
//实例化的只是普通到了Bean,该部分代码省略
}
else {
//断点位置,开始实例化bean
getBean(beanName);
}
}
}
//源码:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// (第一次)getSignleton获取单例对象
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
//若是获取到转换为bean对象
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
//省略无关代码....
else{
//开始创建实例(缓存中获取不到)
if (mbd.isSingleton()) {
//(第二次)getSignleton获取Bean对象
sharedInstance = getSingleton(beanName, () -> {
try {
//lambda表达式,可以理解为策略对象,会在getSingleton某处回调createBean方法。
return createBean(beanName, mbd, args);
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
//创建完毕。返回最终的bean实例
return (T) bean;
}
2.1 两次getSingleton方法分析
实际上,根据上述代码可以得到,Bean对象的创建是在getSingleton方法中。
1. 第一次调用getSingleton(String)方法:
public Object getSingleton(String beanName) {
//默认传入true,即allowEarlyReference允许循环依赖
return getSingleton(beanName, true);
}
实际是重载方法,allowEarlyReference
为true,即允许在二级缓存中获取Object。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//在单例池(一级缓存)中获取bean
Object singletonObject = this.singletonObjects.get(beanName);
//由于首次创建,单例池为null,并且判断该bean是否被创建?
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//若对象正在创建,那么在三级缓存中获取对象
singletonObject = this.earlySingletonObjects.get(beanName);
//三级缓存不存在,并且允许 使用早期引用
if (singletonObject == null && allowEarlyReference) {
//获取单例工厂(里面包含加工Bean的流水线)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//将Object增加AOP代理。暴露Object。(分析如下面代码所示)
singletonObject = singletonFactory.getObject();
//填充到第三级缓存中,之后均在第三级缓存中获取
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
三级缓存流程图.png
调用getBean()
获取单例Bean时,Spring首先判断单例池(也就是一级缓存中)是否存在。若是不存在,去二级或者三级缓存中获取earlySingletonObjects
。
三级缓存的概念:
缓存级别 | 作用 |
---|---|
singletonObjects:一级缓存 | 可以理解为常说的Spring容器,即单例池 |
singletonFactories:二级缓存 | 缓存的是单例工厂,对Object进行代理 |
earlySingletonObjects:三级缓存 | 得到二级缓存处理的代理对象;作为循环依赖的标识 |
提前代理Object:
一般来说,对目标对象的代理是发生在如图所示的(4)步中:
image.png
但是发生循环依赖,正在创建的Bean需要再次创建,作为属性注入到其他Bean中。
于是在二级缓存取出Object时,会对Object进行AOP处理,这个过程也叫做提前暴露Object,或者也可以称为earlySingletonObject
。
//源码:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference
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;
//调用后置处理器的包装方法,对Bean进行AOP代理
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
理论上:二级缓存获取并存入三级缓存的
exposedObject
(代理)对象,最终会存入Spring容器中。若是正常流程AOP代理后也产生了一个代理对象,会抛出自检异常!
2. 第二次调用getSingleton(String,ObjectFactory<?>)
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
//在一级缓存中未获取到Bean对象
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//(重点)标记该Bean正则创建
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
//该方法是lamdba表达式的回调方法。
//createBean()真正开始创建Bean容器
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
...
return singletonObject;
}
}
2.2 创建Bean分析
//源码:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
try {
//开始创建Bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
}
真正的重点:创建Bean!
实际上,上述代码是在缓存中获取bean对象。而实际上真正创建bean对象的核心代码是下面的代码。
如图2.2.1 所示,一个Bean经过流水线后最终会获取到暴露的Bean(exposedObject)对象。
图2.2.1-Bean流水线—普通bean创建流程.png如图2.2.2 所示,在(2)属性注入时,在实例化的过程(发生了循环依赖)中。调用getSingleton(String)方法,此时Bean正在创建,于是会在第二级缓存中(singletonFactory)中生产出经过AOP处理的Object对象。来完成Bean的属性注入。
需要注意,将Object存入二级缓存时,存的是final类型的对象,即不会(4)中对Object的操作不会影响(1)中的Object对象。
图2.2.2-Bean流水线—bean循环依赖解决方案.png //源码:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
if (instanceWrapper == null) {
//1. 通过反射构造函数,创建出对象。可以理解为new Object()
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//获取到Object对象
final Object bean = instanceWrapper.getWrappedInstance();
//2. 该对象是单例对象,并且允许循环依赖[allowCircularReferences],并且正在创建
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
//将final Object放入到二级缓存中。放的不是普通的对象,而是表达式,即一组逻辑
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// bean是final类型,需赋给一个普通Object
Object exposedObject = bean;
try {
//3. 属性注入(去实例化属性对象)
populateBean(beanName, mbd, instanceWrapper);
//4. 声明周期的回调方法(执行后置处理器,进行AOP代理)
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
若无发生循环依赖,是不会存在自检操作的:
1. 如何判断发生循环依赖:第三级缓存(earlySingtonObject)中若存在对象,则证明发生了循环依赖。
2.为什么要进行自检操作:对象在被二级缓存返回的singletonFactory
进行了AOP处理,并作为属性注入到其他Bean。之后会执行正常流程的AOP代理操作
。若再被其他拦截器进行增强。就会导致当做属性注入的bean
和单例池中的bean
版本不同。
3. 是否可以关闭自检操作:是可以关闭的,但是有风险。
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
}
}
正是(10)中的自检操作。导致了我们项目异常。
Object进行AOP代理的地方.png若两处均生成代理对象,earlySingletonObject
已经做完属性注入到其他Bean中,但是好像SingletonObject
(正常流程下AOP代理)是最新版本。便会产生自检异常。
//允许循环依赖
if (earlySingletonExposure) {
//只去第三级缓存中[earlySingletonObjects]获取暴露的(代理)对象。
Object earlySingletonReference = getSingleton(beanName, false);
//如果获取到,即发生了循环依赖,若没发送循环依赖,是不会存在自检的
if (earlySingletonReference != null) {
//正常流程得到的exposedObject不应该被代理
if (exposedObject == bean) {
//最终暴露出三级缓存的Object。
exposedObject = earlySingletonReference;
}else{
}
//若正常流程下依旧会对Object进行包装
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 " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
return exposedObject;
}
3. bug分析
自检异常出现的根本原因是:
在Bean的生命周期中,有两处地方object可以进行AOP代理。
- 正常流程下生命周期回调方法结束后执行
BeanPostProcessor
的postProcessAfterInitialization
进行AOP代理,生成代理对象。 -
循环依赖情况下,在二级缓存
singletonFactories
生成对象时,会执行SmartInstantiationAwareBeanPostProcessor
的getEarlyBeanReference
进行AOP代理,目的是将代理对象注入到Bean中,解决循环依赖。
若循环依赖,提前会进行AOP代理,生成了
earlySingletonObject
代理对象。并且注入到Bean中。若正常流程下又获取到了新的代理对象,那么Spring便不知道以哪个代理版本为主。便会抛出自检异常。
1. 事务注解导致正常流程下object被代理吗?
于是我们打开源码:AnnotationAwareAspectJAutoProxyCreator
事务的后置处理器:它的后置处理逻辑是在AbstractAutoProxyCreator
类中
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
//循环依赖—早期暴露Object的代理方法
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
//正常流程— 对Object进行AOP代理的方法
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
//若是在二级缓存中进行了代理,则此处不进行代理!
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
}
但是源码中明确指出:若是Object在循环依赖中被该方法提前代理,在正常流程的AOP代理时,是不会进行代理的!
按照源码来说,实际上即使存在事务方法,并且发生了循环依赖,也可以启动成功。
因为事务的后置处理器已经考虑到这种情况并且解决了。
2. 正常流程object为什么会被代理
于是对正常流程下进行AOP代理的方法上加上端点。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
执行DefaultAdvisorAutoProxyCreator(也是AbstractAutoProxyCreator子类)
时,根据cacheKey获取到的是代理对象。与目标对象不相等。于是被DefaultAdvisorAutoProxyCreator
代理。
3. 二级缓存对Objecy进行AOP代理时,DefaultAdvisorAutoProxyCreator为什么存储代理对象,而不是目标对象。
在二级缓存生成代理对象时,因为Object要经过后置处理器层层代理,会经历如下流程:导致map(earlyProxyReferences)
存储的是代理对象。
4. DefaultAdvisorAutoProxyCreator何时注册到Spring容器中
@ConditionalOnProperty("spring.datasource.druid.aop-patterns")
public class DruidSpringAopConfiguration {
@Bean
public Advice advice() {
return new DruidStatInterceptor();
}
@Bean
public Advisor advisor(DruidStatProperties properties) {
return new RegexpMethodPointcutAdvisor(properties.getAopPatterns(), advice());
}
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
}
原来是Durid下的包,并且是因为自己在配置文件中配置了spring.datasource.druid.aop-patterns=xxx
属性。导致该后置处理器加载到容器中。
打开配置文件一看,果真存在该配置,删除该配置后。项目可以重写启动。
“若不想删除,可以在循环依赖的属性上使用@Lazy懒加载。”
4. 归纳总结
该bug就是因为一条durid配置,并且项目中存在事务注解的类循环依赖导致项目重启失败。
解决问题的过程中,也弄清了Spring如何解决循环依赖。
“Spring创建Bean的过程,就是先在缓存中取,取不到开始创建。”
三级缓存的作用:
-
一级缓存:单例池,即常说的spring容器。
-
二级缓存:若允许循环依赖,将singletonFactory存入二级缓存。目的是延迟加载,真正发生循环依赖时对对象进行代理操作。
-
三级缓存:防止二级缓存多次处理,并且标示发生过循环依赖,以便后续spring自检。
推荐阅读
一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】
使用@Async异步注解导致该Bean在循环依赖时启动报BeanCurrentlyInCreationException异常的根本原因分析,以及提供解决方案【享学Spring】
网友评论