Resolution of Spring Boot 2.0.0 Upgrade to 2.4.1 Circular Dependency

Keywords: Java Spring Boot

Catalog

Resolution of Spring Boot 2.0.0 Upgrade to 2.4.1 Circular Dependency

describe

Analysis

First Cause

Second Cause

Third-tier Reason

Layer 4 Reasons

Layer Five Reasons

Layer Six Reasons

Layer 7 Reasons

Final cause

Solution

Comparing Relevant Versions

Spring Boot 2.0.0.release + Druid 1.19

Spring Boot 2.4.1 + Druid 1.19

Spring Boot 2.4.1 + Druid 1.2.5

Spring Boot 2.4.1 + Druid 1.2.5 + spring.aop.auto=false

summary

Resolution of Spring Boot 2.0.0 Upgrade to 2.4.1 Circular Dependency

describe

A circular dependency error occurred during the upgrade of Spring boot 2.0.0 to 2.4.1 with the following contents:

Error creating bean with name 'test1ServiceImpl': Bean with name 'test1ServiceImpl' has been injected into other beans [test2ServiceImpl] 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.

Analysis

Code in question:

@Service
@Log4j2
public class Test1ServiceImpl implements ITest1Service {

    @Autowired
    private ITest2Service test2Service;

    @Cacheable(cacheNames="getNameById")
    @Override public String getNameById(Integer id) {
        Integer newId = test2Service.getId();
        if(id.equals(newId)){
            return "New Zhang San";
        }else{
            return "Zhang San";
        }
    }

    @Override public List<String> getAllNames() {
        return null;
    }
}


@Service
@Log4j2
public class Test2ServiceImpl implements ITest2Service {
    @Autowired
    private ITest1Service test1Service;

    @Transactional
    @Override public Integer getId() {
        return new Random().nextInt(3);
    }

    @Override public List<Integer> getAllIds() {
        System.out.println(test1Service.getAllNames());
        return null;
    }
}

The surface problem is that Test1Service and Test2Service depend on each other, resulting in circular dependency. In fact, this simple circular dependency has been resolved by Spring using a multilevel caching method, so it should not normally happen.

By tracing the relevant code, the trigger point for the problem is in the method of org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean, which has the following source code:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// Allow post-processors to modify the merged bean definition.
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// 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.isDebugEnabled()) {
				logger.debug("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 && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", 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 " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

First Cause

Analysis code found that exposedObject and bean were not caused by the same object.

Related code:

		//Determine whether exposure to early objects is necessary
		if (earlySingletonExposure) {
		   //Read-only method to get early objects (false means no creation if none exists), from first-level and second-level caches
		   //At this point, beans usually exist, and you get an enhanced Bean object
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
			    //If the exposed Bean is still the original Bean
				if (exposedObject == bean) {
				    //Replace with enhanced Bean object
					exposedObject = earlySingletonReference;
				}
				//If the original Bean injection is not allowed after the enhanced beans exist, and the beans contain dependent beans
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					//Get a list of dependent beans
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
					    //If the dependent beans have been created successfully
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
						    //Increase Actually Dependent Bean s
							actualDependentBeans.add(dependentBean);
						}
					}
					//If the beans that actually depend on are not empty, throw the circular dependency exception
					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.");
					}
				}
			}
		}

Version 2.0.0 of the org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean) code:

	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

Version 2.4.1 of the org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean) code:

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;
	}

Second Cause

Further analysis Why exposedObject and bean are not the same object?

The code is as follows:

// Initialize the bean instance.
		//Set exposed beans to original beans
		Object exposedObject = bean;
		try {
		    //Perfecting bean information, including injecting dependent beans, recursively initializes dependent beans
			populateBean(beanName, mbd, instanceWrapper);
			//When the Bean is created (including enhancement), the returned Bean is not the original Bean, but the enhanced Bean
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

Third-tier Reason

Further analysis of why the org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition) method returned an enhanced Bean was performed. It was found that the applyBeanPostProcessorsAfterInitialization method returned an enhanced Bean.

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			//Call Aware-related methods
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			// Postprocessor for Bean s invoked before initialization
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			//After initialization, the Bean's postprocessor is called, where the enhanced Bean is returned
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}

Layer 4 Reasons

Further track the org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization method to determine which enhanced Bean was returned by the rear processor. Found is org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization of the DefaultAdvisorAutoProxyCreator classThe method returns an enhanced Bean.

	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			//Call all the post processors to enhance the beans by overlaying them
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

Layer Five Reasons

Further tracking of the DefaultAdvisorAutoProxyCreator class org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization method reveals that the same BeanName but different Beans for different objects already exist in the secondary cache earlyProxyReferences.

This judges only BeanName in version 2.0.0, and wrapped Bean processing will not be performed as long as BeanName is consistent. In version 2.4.1 both BeanName and instance object consistency must be satisfied.

Version 2.4.1:

	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			//Remove a Bean with the same name from the secondary cache and determine if the removed Bean and the current Bean are the same Bean
			// Due to circular dependency, the current Bean has been initialized to the secondary cache by the circularly dependent Bean, and the Bean initialized to the secondary cache is an enhanced Bean
			//So although a Bean with the same name exists, the instances of the beans are not the same, so wrapped beans are still processed and returned to the wrapped beans
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			   //If the Bean does not exist in the secondary cache or if it is not the same object as the current Bean, wrap the Bean and return the wrapped Bean
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

Version 2.0.0 code:

	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			//Determine whether a Bean with the same name exists in the secondary cache
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				//If not, wrap the beans and return the wrapped beans
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

Layer Six Reasons

Continuing to track the creation process of test1ServiceImpl Bean, we found that test1ServiceImpl called the org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean method twice because of circular dependency.

The call stack is as follows:

getEarlyBeanReference:240, AbstractAutoProxyCreator (org.springframework.aop.framework.autoproxy)
getEarlyBeanReference:974, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doCreateBean$1:602, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 393317990 (org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$$Lambda$279)
getSingleton:194, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
getSingleton:168, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:256, AbstractBeanFactory (org.springframework.beans.factory.support) [4] -> "test1ServiceImpl"
getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:276, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1367, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1287, DefaultListableBeanFactory (org.springframework.beans.factory.support)
inject:640, AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement (org.springframework.beans.factory.annotation)
inject:119, InjectionMetadata (org.springframework.beans.factory.annotation)
postProcessProperties:399, AutowiredAnnotationBeanPostProcessor (org.springframework.beans.factory.annotation)
populateBean:1415, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:608, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:531, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 275091441 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$278)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support) [3] -> "test2ServiceImpl"
getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:276, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1367, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1287, DefaultListableBeanFactory (org.springframework.beans.factory.support)
inject:640, AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement (org.springframework.beans.factory.annotation)
inject:119, InjectionMetadata (org.springframework.beans.factory.annotation)
postProcessProperties:399, AutowiredAnnotationBeanPostProcessor (org.springframework.beans.factory.annotation)
populateBean:1415, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:608, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:531, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 275091441 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$278)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support) [2]  -> "test1ServiceImpl"
getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:276, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1367, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1287, DefaultListableBeanFactory (org.springframework.beans.factory.support)
inject:640, AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement (org.springframework.beans.factory.annotation)
inject:119, InjectionMetadata (org.springframework.beans.factory.annotation)
postProcessProperties:399, AutowiredAnnotationBeanPostProcessor (org.springframework.beans.factory.annotation)
populateBean:1415, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:608, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:531, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 275091441 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$278)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support) [1]  -> "testController"
getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:944, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:923, AbstractApplicationContext (org.springframework.context.support)
refresh:588, AbstractApplicationContext (org.springframework.context.support)
refresh:144, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:767, SpringApplication (org.springframework.boot)
refresh:759, SpringApplication (org.springframework.boot)
refreshContext:426, SpringApplication (org.springframework.boot)
run:326, SpringApplication (org.springframework.boot)
run:1309, SpringApplication (org.springframework.boot)
run:1298, SpringApplication (org.springframework.boot)
main:26, DemoApplication (com.example.demo)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:564, Method (java.lang.reflect)
run:49, RestartLauncher (org.springframework.boot.devtools.restart)

The doGetBean code is as follows:

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		//The first call returns null because there are no beans in the cache, and the second call takes the beans and puts them into the cache based on the first Bean's construction parameters.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
		    //Enter on second call
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			//Enter on first call
			// Fail if we're already creating this bean instance:
			// We're assumably within a circular reference.
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// Check if bean definition exists in this factory.
			BeanFactory parentBeanFactory = getParentBeanFactory();
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}

			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}

			StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
					.tag("beanName", name);
			try {
				if (requiredType != null) {
					beanCreation.tag("beanType", requiredType::toString);
				}
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						registerDependentBean(dep, beanName);
						try {
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}

				// Create bean instance.
				if (mbd.isSingleton()) {
				    //On the first invocation, prepare the Bean's construction methods and associated construction parameters to create the Bean
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ยด" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new ScopeNotActiveException(beanName, scopeName, ex);
					}
				}
			}
			catch (BeansException ex) {
				beanCreation.tag("exception", ex.getClass().toString());
				beanCreation.tag("message", String.valueOf(ex.getMessage()));
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
			finally {
				beanCreation.end();
			}
		}

		// Check if required type matches the type of the actual bean instance.
		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				if (logger.isTraceEnabled()) {
					logger.trace("Failed to convert bean '" + name + "' to required type '" +
							ClassUtils.getQualifiedName(requiredType) + "'", ex);
				}
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

Layer 7 Reasons

Track Spring Boot 2.0.0 and Spring Boot on the second Bean Build2.4.1 Difference found that when calling the org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference method, the caching strategies in the org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference method are different, as are the caching strategies in the org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreate method, and the org.springframework.The or#postProcessAfterInitializationf method determines whether beans have cached policies that are different, causing the same code to have no circular dependencies at 2.0.0 but 2.4.1 to have circular dependencies.

Version 2.0.0 of the org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference method:

	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;
	}

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference method:

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (!this.earlyProxyReferences.contains(cacheKey)) {
			this.earlyProxyReferences.add(cacheKey);
		}
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization method:

	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

Version 2.4.1

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference method:

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference method:

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization method:

	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;
	}

Final cause

At 2.0.0, the processing of cyclically dependent Bean initialization:

1. On the second initialization, when the DefaultAdvisorAutoProxyCreator class calls the getEarlyBeanReference method of the parent class, it completes the initialization of the enhanced Bean and adds the earlyProxyReferences cache of the instances of the DefaultAdvisorAutoProxyCreator class, caching the BeanName, and caching the instances of the enhanced Bean to the earlySingle of the DefaultSingletonBeanRegistry.In tonObjects.

        //Determine whether a Bean is cached based on BeanName
		if (!this.earlyProxyReferences.contains(cacheKey)) {
		    //Cache BeanName if not cached
			this.earlyProxyReferences.add(cacheKey);
		}

2. When the first initialization is returned, the DefaultAdvisorAutoProxyCreator class calls the postProcessAfterInitialization method of the parent class to determine whether the Bean is cached based on the BeanName, since the BeanName has been cached by the previous second initialization. The enhanced Bean is not returned.

	//Determine whether a Bean is cached based on BeanName
	if (!this.earlyProxyReferences.contains(cacheKey)) {
		//If not cached, return enhanced Bean
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

3. Finally, when the consistency between the initialized beans and the original beans is determined in the square of org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean, the result is consistent and the enhanced beans are returned. The circular dependency exception is not triggered.

// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			//Fill Dependent Bean s
			populateBean(beanName, mbd, instanceWrapper);
			//Get Initialized Bean s
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			//Get cached enhanced beans
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					//If the original and initialized beans are identical, the enhanced beans are returned
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					//Otherwise trigger the circular dependency exception
					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 " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

At 2.4.1, the processing of cyclically dependent Bean initialization:

1. At the second initialization, when the DefaultAdvisorAutoProxyCreator class invokes the parent class's getEarlyBeanReference method, the enhanced Bean is initialized and added to the earlyProxyReferences cache of instances of the DefaultAdvisorAutoProxyCreator class, cached with BeanName and the enhanced Bean, which is added by instances of AnnotationAwareAspectoJAutoProxyCreatorStrong, caching instances of enhanced beans into earlySingletonObjects of DefaultSingletonBeanRegistry at the same time.

       
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		//Without judgment, directly cache BeanName and enhanced Bean
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

2. When returning to the first initialization, the DefaultAdvisorAutoProxyCreator class calls the postProcessAfterInitialization method of the parent class to determine whether the Bean is cached based on the BeanName and the instance object. Since the BeanName and the enhanced Bean instances have been cached by the second initialization before, although BeanName is consistent, the current instance and the cached enhanced instanceNot the same instance, so return the enhanced Bean.

	
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	//Determine whether a Bean is cached based on BeanName and instance objects
	if (this.earlyProxyReferences.remove(cacheKey) != bean) {
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

3. Finally, when the consistency between the initialized beans and the original beans is judged in the square of org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean, the result is inconsistent and the circular dependency exception is triggered.

// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			//Fill Dependent Bean s
			populateBean(beanName, mbd, instanceWrapper);
			//Get Initialized Bean s
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			//Get cached enhanced beans
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					//If the original and initialized beans are identical, the enhanced beans are returned
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					//Otherwise trigger the circular dependency exception
					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 " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

Solution

Upgrading druid versions from 1.1.9 to 1.2.5 is sufficient. The reason is that the 1.2.5 DruidSpringAopConfiguration class adapts the policy for automatic loading of DefaultAdvisorAutoProxyCreator by using Spring by default to manage AOP enhancements instead of DefaultAdvisorAutoProxyCreator by default. The code for this is as follows:

1.1.9 Code:

    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

1.2.5 Code:

    @Bean
    @ConditionalOnProperty(name = "spring.aop.auto",havingValue = "false")
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

Comparing Relevant Versions

Spring Boot 2.0.0.release + Druid 1.19

No circular dependencies

getBeanPostProcessors() = {ArrayList@6940}  size = 19
 0 = {ApplicationContextAwareProcessor@9155} 
 1 = {WebApplicationContextServletContextAwareProcessor@9156} 
 2 = {ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor@9157} 
 3 = {PostProcessorRegistrationDelegate$BeanPostProcessorChecker@9158} 
 4 = {ConfigurationPropertiesBindingPostProcessor@5223} 
 5 = {InfrastructureAdvisorAutoProxyCreator@5357} "proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 6 = {DataSourceInitializerPostProcessor@5407} 
 7 = {AsyncAnnotationBeanPostProcessor@5318} "proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 8 = {MethodValidationPostProcessor@5384} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 9 = {DefaultAdvisorAutoProxyCreator@5397} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 10 = {PersistenceExceptionTranslationPostProcessor@5412} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 11 = {ObjectMapperConfigurer@5420} 
 12 = {WebServerFactoryCustomizerBeanPostProcessor@6140} 
 13 = {ErrorPageRegistrarBeanPostProcessor@6144} 
 14 = {CommonAnnotationBeanPostProcessor@5219} 
 15 = {AutowiredAnnotationBeanPostProcessor@5209} 
 16 = {RequiredAnnotationBeanPostProcessor@5213} 
 17 = {ScheduledAnnotationBeanPostProcessor@5282} 
 18 = {ApplicationListenerDetector@9159} 

Spring Boot 2.4.1 + Druid 1.19

Problems with circular dependency

getBeanPostProcessorCache().smartInstantiationAware = {ArrayList@9857}  size = 3
 0 = {AnnotationAwareAspectJAutoProxyCreator@5452} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 1 = {DefaultAdvisorAutoProxyCreator@5547} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 2 = {AutowiredAnnotationBeanPostProcessor@5292} 
 
 
 getBeanPostProcessors() = {AbstractBeanFactory$BeanPostProcessorCacheAwareList@7531}  size = 18
 0 = {ApplicationContextAwareProcessor@10106} 
 1 = {WebApplicationContextServletContextAwareProcessor@10111} 
 2 = {ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor@10112} 
 3 = {PostProcessorRegistrationDelegate$BeanPostProcessorChecker@10113} 
 4 = {ConfigurationPropertiesBindingPostProcessor@5310} 
 5 = {AnnotationAwareAspectJAutoProxyCreator@5452} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 6 = {DataSourceInitializerPostProcessor@5557} 
 7 = {AsyncAnnotationBeanPostProcessor@5402} "proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 8 = {FilteredMethodValidationPostProcessor@5527} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 9 = {DefaultAdvisorAutoProxyCreator@5547} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 10 = {PersistenceExceptionTranslationPostProcessor@5564} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 11 = {ObjectMapperConfigurer@6603} 
 12 = {WebServerFactoryCustomizerBeanPostProcessor@6609} 
 13 = {ErrorPageRegistrarBeanPostProcessor@6615} 
 14 = {CommonAnnotationBeanPostProcessor@5304} 
 15 = {AutowiredAnnotationBeanPostProcessor@5292} 
 16 = {ScheduledAnnotationBeanPostProcessor@5353} 
 17 = {ApplicationListenerDetector@10114} 

Spring Boot 2.4.1 + Druid 1.2.5

No circular dependencies

getBeanPostProcessorCache().smartInstantiationAware = {ArrayList@9840}  size = 2
 0 = {AnnotationAwareAspectJAutoProxyCreator@5451} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 1 = {AutowiredAnnotationBeanPostProcessor@5291} 
 
 
getBeanPostProcessors() = {AbstractBeanFactory$BeanPostProcessorCacheAwareList@7512}  size = 17
 0 = {ApplicationContextAwareProcessor@10069} 
 1 = {WebApplicationContextServletContextAwareProcessor@10074} 
 2 = {ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor@10075} 
 3 = {PostProcessorRegistrationDelegate$BeanPostProcessorChecker@10076} 
 4 = {ConfigurationPropertiesBindingPostProcessor@5309} 
 5 = {AnnotationAwareAspectJAutoProxyCreator@5451} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 6 = {DataSourceInitializerPostProcessor@5538} 
 7 = {AsyncAnnotationBeanPostProcessor@5401} "proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 8 = {FilteredMethodValidationPostProcessor@5526} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 9 = {PersistenceExceptionTranslationPostProcessor@5545} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 10 = {ObjectMapperConfigurer@6579} 
 11 = {WebServerFactoryCustomizerBeanPostProcessor@6585} 
 12 = {ErrorPageRegistrarBeanPostProcessor@6591} 
 13 = {CommonAnnotationBeanPostProcessor@5303} 
 14 = {AutowiredAnnotationBeanPostProcessor@5291} 
 15 = {ScheduledAnnotationBeanPostProcessor@5352} 
 16 = {ApplicationListenerDetector@10077}  

Spring Boot 2.4.1 + Druid 1.2.5 + spring.aop.auto=false

Problems with circular dependency

getBeanPostProcessorCache().smartInstantiationAware = {ArrayList@9756}  size = 3
 0 = {InfrastructureAdvisorAutoProxyCreator@5446} "proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 1 = {DefaultAdvisorAutoProxyCreator@5527} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 2 = {AutowiredAnnotationBeanPostProcessor@5288} 
 
getBeanPostProcessors() = {AbstractBeanFactory$BeanPostProcessorCacheAwareList@7457}  size = 18
 0 = {ApplicationContextAwareProcessor@9796} 
 1 = {WebApplicationContextServletContextAwareProcessor@9797} 
 2 = {ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor@9798} 
 3 = {PostProcessorRegistrationDelegate$BeanPostProcessorChecker@9799} 
 4 = {ConfigurationPropertiesBindingPostProcessor@5306} 
 5 = {InfrastructureAdvisorAutoProxyCreator@5446} "proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 6 = {DataSourceInitializerPostProcessor@5533} 
 7 = {AsyncAnnotationBeanPostProcessor@5398} "proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 8 = {FilteredMethodValidationPostProcessor@5506} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 9 = {DefaultAdvisorAutoProxyCreator@5527} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 10 = {PersistenceExceptionTranslationPostProcessor@5540} "proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false"
 11 = {ObjectMapperConfigurer@5550} 
 12 = {WebServerFactoryCustomizerBeanPostProcessor@6522} 
 13 = {ErrorPageRegistrarBeanPostProcessor@6528} 
 14 = {CommonAnnotationBeanPostProcessor@5300} 
 15 = {AutowiredAnnotationBeanPostProcessor@5288} 
 16 = {ScheduledAnnotationBeanPostProcessor@5349} 
 17 = {ApplicationListenerDetector@9800}   
 

summary

Spring enhances beans by the AnnotationAwareAspectJAutoProxyCreator class when dealing with circularly dependent beans, but the AnnotationAwareAspectJAutoProxyCreator class itself caches the original beans if both AnnotationAwareAspectJAutoProxyCreator and DefaultAdvisorAutoProxyCreator exist, and AnnotationAwareAspectJAutoProxyCreator is in DefaultisorAutoP AdvisorPrevious execution of roxyCreator causes DefaultAdvisorAutoProxyCreator to cache beans enhanced by AnnotationAwareAspectJAutoProxyCreator, which means that the beans have been enhanced twice, and the rules for determining whether the beans are cached have changed after 2.4.1, which increases the logic for determining whether the beans are the same instance, eventually causing an exception of Bean circular dependency.

Posted by undecided name 01 on Fri, 17 Sep 2021 21:02:47 -0700