What is circular dependency?
Circular dependency: during dependency injection, multiple Bean objects hold references to each other. For example, object A contains object B, object B contains object A, and so on. They look like A ring, connected end to end.
After understanding this, you may ask: what problems will this situation cause?
The so-called knowing what is and why is, so we still need to think about the reasons for it before solving the problem.
Circular dependency in Spring
reason
First, we need to combine the instantiation rules of beans in Spring, and the previous article on parsing Spring dependency injection is in the source code( DI chapter of Spring source code analysis If you don't know the DI principle, you are recommended to understand it first and then look at the circular dependency. I believe it is easier to understand it). You already know that Spring will instantiate the dependent Bean object before pre instantiating the Bean. What will happen in this way? Let's draw briefly,
Is something wrong found? They are like a loop recursion. Unless there is an end condition, they are dead loops until there is a memory overflow exception.
Occurrence scenario and analysis
Now that you know the cause of the problem, anxious friends may directly consider the Spring solution, but don't worry. This is only a preliminary idea. We also have to analyze the circular dependency in different scenarios. As mentioned above, in some cases, the dependent Bean object will be instantiated before instantiating a Bean. What will happen? Next, let's list and verify them respectively.
Firstly, we know that the Scope of Bean in Spring can be divided into three types: singleton, prototype and others (request, session, application and websocket). In the source code of Bean instantiation, different instantiation strategies are selected according to these three scopes. Take a look at the source code (irrelevant code is omitted temporarily)
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { ...... //Create a singleton Bean object if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { //Create Bean instance return createBean(beanName, mbd, args); } catch (BeansException ex) { //Clear Bean instance destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } //Create a prototype Bean object else if (mbd.isPrototype()) { Object prototypeInstance = null; try { //Callback before creation. By default, beanName is registered as the prototype Bean being created beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { //After the callback is created, the beanName of the newly registered prototype Bean is removed by default afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } //Non singleton and non prototype beans created else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); //If the scope is not configured, an illegal exception is thrown 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 BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } ...... return (T) bean; }
Compared with a single instance that only saves one object, the prototype and others will generate multiple objects. The following scenarios will also verify the single instance and multiple instances (prototype) respectively.
Before we start, let's introduce two properties that will be used:
/** Save the beanName of the singleton Bean being created */ private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); /** Save the beanName of the prototype Bean being created, where Object may be a String or set < String >*/ private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation");
Parametric construction method injection
First, we prepare the test class as follows:
@Component public class BeanA { @Autowired private BeanB beanB; public BeanA(BeanB beanB) { this.beanB = beanB; } public BeanB getBeanB() { return beanB; } } @Component public class BeanB { @Autowired private BeanA beanA; public BeanB(BeanA beanA) { this.beanA = beanA; } public BeanA getBeanA() { return beanA; } } public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("com.test.spring.entity"); BeanA beanA = (BeanA) ac.getBean("beanA"); BeanB beanB = (BeanB) ac.getBean("beanB"); System.out.println(beanA); System.out.println(beanB); System.out.println(beanB.getBeanA()); System.out.println(beanA.getBeanB()); } }
Run the code and throw the following exception,
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
It seems that the constructor injection of the singleton has a circular dependency error. Let's take a look at the prototype types. Add them to the classes of BeanA and BeanB respectively
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Run the test code and find that the above error exception will also be thrown.
Now that we know the results, let's first analyze the source code flow chart that generates exceptions (the source code here will not be pasted in detail, including the flow chart of the following scenarios, which mainly focuses on the directly important related points. Those pursuing code details can be combined with the previous DI article or Spring source code),
Before creating an object, we will first judge whether the Bean to be created is being created through the isSingletonCurrentlyInCreation() or isPrototypeCurrentlyInCreation() methods. If it is not, we will save the Bean name of the current Bean to singletonscrurrentlyincreation or prototypes currentlyincreation; Since we are injected through the parametric construction method (see yellow logo in the picture), we call the autowireConstructor() method, and the instantiation of the trigger construction method parameter BeanB takes place here. We know that its main function is to determine the construction method parameters first, then pass the parameters to determine the construction method, then convert the parameters into the required type, and finally call strategy.. Instantiate() completes instantiation. There is an important variable argumentholder in the method, which encapsulates the parameters required to construct the method. Its creation and initialization are completed by calling createArgumentArray(),
private ArgumentsHolder createArgumentArray( String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues, BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable, boolean autowiring) throws UnsatisfiedDependencyException { TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = (customConverter != null ? customConverter : bw); ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length); Set<String> autowiredBeanNames = new LinkedHashSet<>(4); for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { Class<?> paramType = paramTypes[paramIndex]; String paramName = (paramNames != null ? paramNames[paramIndex] : ""); // Try to find matching constructor argument value, either indexed or generic. ConstructorArgumentValues.ValueHolder valueHolder = null; if (resolvedValues != null) { valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders); // If we couldn't find a direct match and are not supposed to autowire, // let's try the next generic, untyped argument value as fallback: // it could match after type conversion (for example, String -> int). if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) { valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders); } } if (valueHolder != null) { // We found a potential match - let's give it a try. // Do not consider the same value definition multiple times! usedValueHolders.add(valueHolder); Object originalValue = valueHolder.getValue(); Object convertedValue; if (valueHolder.isConverted()) { convertedValue = valueHolder.getConvertedValue(); args.preparedArguments[paramIndex] = convertedValue; } else { MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); try { convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam); } catch (TypeMismatchException ex) { throw new UnsatisfiedDependencyException( mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(valueHolder.getValue()) + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); } Object sourceHolder = valueHolder.getSource(); if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) { Object sourceValue = ((ConstructorArgumentValues.ValueHolder) sourceHolder).getValue(); args.resolveNecessary = true; args.preparedArguments[paramIndex] = sourceValue; } } args.arguments[paramIndex] = convertedValue; args.rawArguments[paramIndex] = originalValue; } else { MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); // No explicit match found: we're either supposed to autowire or // have to fail creating an argument array for the given constructor. if (!autowiring) { throw new UnsatisfiedDependencyException( mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), "Ambiguous argument values for parameter of type [" + paramType.getName() + "] - did you specify the correct bean references as arguments?"); } try { Object autowiredArgument = resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter); args.rawArguments[paramIndex] = autowiredArgument; args.arguments[paramIndex] = autowiredArgument; args.preparedArguments[paramIndex] = new AutowiredArgumentMarker(); args.resolveNecessary = true; } catch (BeansException ex) { throw new UnsatisfiedDependencyException( mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex); } } } for (String autowiredBeanName : autowiredBeanNames) { this.beanFactory.registerDependentBean(autowiredBeanName, beanName); if (this.beanFactory.logger.isDebugEnabled()) { this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName + "' via " + (executable instanceof Constructor ? "constructor" : "factory method") + " to bean named '" + autowiredBeanName + "'"); } } return args; } @Nullable protected Object resolveAutowiredArgument(MethodParameter param, String beanName, @Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter) { if (InjectionPoint.class.isAssignableFrom(param.getParameterType())) { InjectionPoint injectionPoint = currentInjectionPoint.get(); if (injectionPoint == null) { throw new IllegalStateException("No current InjectionPoint available for " + param); } return injectionPoint; } return this.beanFactory.resolveDependency( new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); }
It mainly realizes the resolution of constructor parameter values, and the resolution of the automatically assembled parameter autowiredArgument is really completed by calling the template method resolveDependency(),
@Override @Nullable public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); if (Optional.class == descriptor.getDependencyType()) { return createOptionalDependency(descriptor, requestingBeanName); } else if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) { return new DependencyObjectProvider(descriptor, requestingBeanName); } else if (javaxInjectProviderClass == descriptor.getDependencyType()) { return new Jsr330ProviderFactory().createDependencyProvider(descriptor, requestingBeanName); } else { //Processing objects that require deferred resolution Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; } }
It will select different dependency processing strategies according to the declaration type of parameters. Under normal circumstances, it will go through the doResolveDependency() method,
@Nullable public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); try { Object shortcut = descriptor.resolveShortcut(this); if (shortcut != null) { return shortcut; } Class<?> type = descriptor.getDependencyType(); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { String strVal = resolveEmbeddedValue((String) value); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); value = evaluateBeanDefinitionString(strVal, bd); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()) : converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); } Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); if (multipleBeans != null) { return multipleBeans; } Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (isRequired(descriptor)) { raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } return null; } String autowiredBeanName; Object instanceCandidate; if (matchingBeans.size() > 1) { autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { return descriptor.resolveNotUnique(type, matchingBeans); } else { // In case of an optional Collection/Map, silently ignore a non-unique case: // possibly it was meant to be an empty collection of multiple regular beans // (before 4.3 in particular when we didn't even look for collection beans). return null; } } instanceCandidate = matchingBeans.get(autowiredBeanName); } else { // We have exactly one match. Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); autowiredBeanName = entry.getKey(); instanceCandidate = entry.getValue(); } if (autowiredBeanNames != null) { autowiredBeanNames.add(autowiredBeanName); } // a key if (instanceCandidate instanceof Class) { instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); } Object result = instanceCandidate; if (result instanceof NullBean) { if (isRequired(descriptor)) { raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } result = null; } if (!ClassUtils.isAssignableValue(type, result)) { throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass()); } return result; } finally { ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); } }
This method implements the specific parsing process, including finding and determining the assembled Bean. The focus is on the resolveCandidate() method. Let's click in,
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) throws BeansException { return beanFactory.getBean(beanName); }
The last thing it calls is actually the getBean() method, which is really triggering the instantiation source that depends on Bean. A thousand times and a hundred times, and finally back to the origin.
The next process should also be clear. The recursion will trigger the instantiation of the construction parameter BeanA in BeanB again. When calling the getBean() method to instantiate BeanA, judge the isSingletonCurrentlyInCreation() or isPrototypeCurrentlyInCreation() method, find that BeanA is already being created, and finally throw an exception.
throw new BeanCurrentlyInCreationException(beanName);
Therefore, no matter single or multiple instances, the circular dependency caused by the scenario of parametric construction method injection (that is, constructor injection in Spring) cannot be solved in Spring. However, Spring allows an unconventional method to bypass this deadlock, namely @ Lazy annotation, which will be discussed in detail later.
Parameterless construction method injection
Next, let's look at the scenario of parameter free construction method injection, which is also called setter injection in Spring. First prepare the test class,
@Component public class BeanA { @Autowired private BeanB beanB; public BeanB getBeanB() { return beanB; } public void setBeanB(BeanB beanB) { this.beanB = beanB; } } @Component public class BeanB { @Autowired private BeanA beanA; public BeanA getBeanA() { return beanA; } public void setBeanA(BeanA beanA) { this.beanA = beanA; } } public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("com.test.spring.entity"); BeanA beanA = (BeanA) ac.getBean("beanA"); BeanB beanB = (BeanB) ac.getBean("beanB"); System.out.println(beanA); System.out.println(beanB); System.out.println(beanB.getBeanA()); System.out.println(beanA.getBeanB()); } }
Run the code and print it on the console as follows,
com.test.spring.entity.BeanA@47db50c5 com.test.spring.entity.BeanB@5c072e3f com.test.spring.entity.BeanA@47db50c5 com.test.spring.entity.BeanB@5c072e3f
It seems that the constructor injection of the singleton is normal. Let's take a look at the prototype types. Add them to the classes of BeanA and BeanB respectively
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Run the test code and find that it is different from the single case. An error exception will be thrown here. It seems that the constructor injection of multiple cases has a circular dependency error.
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
Unlike the parametric construction method injection, the single example and prototype will have different results. Next, let's analyze their source code flow charts respectively.
Here we need another directly related attribute. Let's introduce it first:
/** Save the ObjectFactory that created the Bean */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
We have described ObjectFactory in detail in the previous DI article. It is an object factory interface through which we can return instances of objects. In fact, we can often see it in the source code, usually in the form of anonymous internal classes, such as:
sharedInstance = this.getSingleton(beanName, () -> { try { return this.createBean(beanName, mbd, args); } catch (BeansException var5) { this.destroySingleton(beanName); throw var5; } });
-
Singleton:
Different from the parameter construction method injection, the attribute BeanA or BeanB with @ Autowired annotation triggers their instantiation during the attribute injection process. At this time, the createBeanInstance() call has been completed, indicating that the Bean object has been created, but the initialization operation has not been completed and is still in the instantiation process. Let's look at the source code,
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { if (bw == null) { if (mbd.hasPropertyValues()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { return; } } //Post instantiation processing boolean continueWithPropertyPopulation = true; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { continueWithPropertyPopulation = false; break; } } } } if (!continueWithPropertyPopulation) { return; } PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); //Process automatic assembly if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); //Auto injection by name if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } //Automatic injection according to type if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE); if (hasInstAwareBpps || needsDepCheck) { if (pvs == null) { pvs = mbd.getPropertyValues(); } //Attribute value processing PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); if (hasInstAwareBpps) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvs == null) { return; } } } } //Dependency check if (needsDepCheck) { checkDependencies(beanName, mbd, filteredPds, pvs); } } if (pvs != null) { //Attribute injection applyPropertyValues(beanName, mbd, bw, pvs); } }
If the attribute dependency of a Bean is configured through XML, it will trigger the call to getBean() in autowireByName() or autowireByType() to complete the instantiation of the dependent Bean. However, in actual development, we may inject attributes through annotations, such as @ Autowired, @ Value, @ Resouce, etc; Here, we still take @ Autowired in the test class as an example. In the BeanPostProcessor traversing the Bean object, we will get the post processor AutoWiredAnnotationBeanPostProcessor, which will scan the property field configured with @ Autowired annotation, and then call the postProcessPropertyValues() method to automatically inject the property, Let's look at the implementation of postProcessPropertyValues(),
@Override public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException { //Get metadata of autowire related annotations InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { //Attribute injection metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); } return pvs; } public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Collection<InjectedElement> checkedElements = this.checkedElements; Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { boolean debug = logger.isDebugEnabled(); //Traverse the attribute fields to be injected for (InjectedElement element : elementsToIterate) { if (debug) { logger.debug("Processing injected element of bean '" + beanName + "': " + element); } element.inject(target, beanName, pvs); } } }
Through the above code, we can find that the real processing of attribute injection is to call element Inject (target, beanname, PVS). Looking at the source code of element, it is not difficult to find that this InjectedElement class is an internal class of metadata, and it has two subclasses: AutowiredFieldElement and AutowiredMethodElement. They override the inject() method respectively, and they are also the internal class of AutoWiredAnnotationBeanPostProcessor, Among them, AutowiredFieldElement is the annotation processing above the scanning attribute field, and AutowiredMethodElement is the annotation processing above the scanning method parameters. What the test class actually calls here is the inject() method in AutowiredFieldElement,
@Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { //Get property field Field field = (Field) this.member; Object value; //Determine whether to cache in container if (this.cached) { //Get cache value value = resolvedCachedArgument(beanName, this.cachedFieldValue); } else { DependencyDescriptor desc = new DependencyDescriptor(field, this.required); desc.setContainingClass(bean.getClass()); Set<String> autowiredBeanNames = new LinkedHashSet<>(1); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); try { //According to the Bean definition in the container, resolve the specified dependency and obtain the dependent object value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); } synchronized (this) { if (!this.cached) { if (value != null || this.required) { this.cachedFieldValue = desc; //Register dependent beans registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); if (beanFactory.containsBean(autowiredBeanName)) { //Type matching if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { //Create references to dependent objects and cache them this.cachedFieldValue = new ShortcutDependencyDescriptor( desc, autowiredBeanName, field.getType()); } } } } else { this.cachedFieldValue = null; } this.cached = true; } } } if (value != null) { //Set the allowed access through the reflection mechanism of JDK ReflectionUtils.makeAccessible(field); //Attribute field assignment field.set(bean, value); } } }
Have you noticed that the value of value is obtained by calling the template method resolveDependency() method, and this method and its subsequent implementation have been analyzed in detail just now, which is the same as the resolveDependency() processing injected by the parametric construction method. In fact, if you look at the source code of the inject() method in AutowiredMethodElement, you will find that the object injected by the attribute is also obtained through the resolveDependency() method.
So to sum up, we know why the setter loop injection of a singleton is normally allowed.
-
prototype:
In the previous prototype instantiation policy code, we know that the prototype Bean will call beforePrototypeCreation(beanName) before calling createBean() to create, which is used to cache the beanName of the Bean being created. The difference between the prototype and the singleton is that an object will be created in each request, We already know that the cache prototypes currentlyincreation is a thread isolated private variable in ThreadLocal. Spring does not allow it to be created again when the current thread is already creating a prototype Bean, so a circular dependency exception error is thrown here.
@DependsOn annotation
Let's first introduce the function of @ DependsOn annotation: any dependent Bean specifying this annotation is guaranteed to be created by the container before being dependent Bean.
At the same time, we will introduce two properties that will be used:
/** Save the relationship between Bean and dependent Bean */ private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64); /** Save the relationship between dependent beans and dependent beans */ private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
Next, we prepare the test class as follows:
@Component @DependsOn("beanB") public class BeanA { @Autowired private BeanB beanB; public BeanB getBeanB() { return beanB; } } @Component @DependsOn("beanA") public class BeanB { @Autowired private BeanA beanA; public BeanA getBeanA() { return beanA; } } public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("com.test.spring.entity"); BeanA beanA = (BeanA) ac.getBean("beanA"); BeanB beanB = (BeanB) ac.getBean("beanB"); System.out.println(beanA); System.out.println(beanB); System.out.println(beanB.getBeanA()); System.out.println(beanA.getBeanB()); } }
Run the code and throw the following exception,
Circular depends-on relationship between 'beanB' and 'beanA'
It seems that the constructor injection of the singleton has a circular dependency error. Let's take a look at the prototype types. Add them to the classes of BeanA and BeanB respectively
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Run the test code and find that the above error exception will also be thrown.
Next, let's analyze the source code flow chart that generates exceptions,
It is found that the process of abnormal end is very short. Check the source code. In fact, we will deal with the dependency problem before selecting the instantiation strategy of different scopes,
//Get all dependent beans of the Bean 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 + "'"); } //Bean s and dependent beans cache each other registerDependentBean(dep, beanName); //Create dependent Bean getBean(dep); } }
Therefore, no matter the singleton prototype, the exception of circular dependency has been handled in this step. In fact, this is in line with the function description of @ DependsOn annotation in Spring.
Solution
After analyzing the specific source code processing of the above circular dependency scenarios, you should have a general idea of how to deal with and solve circular dependencies in Spring, and whether circular dependencies are allowed or not. In fact, the answer has been given to us in the official documents of Spring (google translate, you can check the original text directly).
Primary and secondary cache
There are some very important attribute fields in the cyclic dependency processing code, which we do not directly mention, but each of the above scenarios is applied. Here we list them uniformly, including those already introduced,
/** Cache instantiated singleton beans */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache singleton beans that have been created but not instantiated */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); /** Cache the ObjectFactory that created the Bean */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache the beanName of the singleton Bean being created */ private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); /** Cache the beanName of the prototype Bean being created, where Object may be a String or set < String >*/ private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation"); /** Cache the whitelist of beans being created*/ private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); /** Relationship between cache Bean and dependent Bean */ private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64); /** Cache the relationship between dependent beans and dependent beans */ private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
If it is classified, I think the first three are the primary cache, from which Bean objects can be obtained directly, and the last five belong to the secondary cache, which is used to record the state information of beans during instantiation; Each of them plays an important role in checking the connection in the processing of Spring circular dependencies to ensure the robustness of Bean instantiation in the container. We know that before instantiating a Bean, we will call the getSingleton() method to try to get the Bean from the cache first, and the role of the main cache is very important. The main cache adopts a three-tier cache, which is vertically layered according to the different states of the Bean during instantiation.
@Nullable 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; }
The following will show the application of these caches in the whole instantiation process through the flowchart,
I don't know if you have ever thought that the cycle dependency error caused by parameter construction method injection or @ DependsOn annotation can't be solved compared with setter injection?
@Lazy annotation
In the previous analysis of the cyclic dependency exception caused by the injection of parametric construction methods, we left a buried point, that is, the problem of exception error reporting can be solved through @ Lazy annotation. First, verify the code,
@Component public class BeanA { @Autowired private BeanB beanB; public BeanA(BeanB beanB) { this.beanB = beanB; } public BeanB getBeanB() { return beanB; } } @Component public class BeanB { @Autowired private BeanA beanA; public BeanB(@Lazy BeanA beanA) { this.beanA = beanA; } public BeanA getBeanA() { return beanA; } } public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("com.test.spring.entity"); BeanA beanA = (BeanA) ac.getBean("beanA"); BeanB beanB = (BeanB) ac.getBean("beanB"); System.out.println(beanA); System.out.println(beanB); System.out.println(beanB.getBeanA()); System.out.println(beanA.getBeanB()); } }
Run the code and the results are as follows. It is found that the results are normal.
com.test.spring.entity.BeanA@7c417213 com.test.spring.entity.BeanB@15761df8 com.test.spring.entity.BeanA@7c417213 com.test.spring.entity.BeanB@15761df8
What's going on?
Let's look back at the previous source code analysis. The constructor parameters are resolved by calling the template method resolveDependency(). There is such a code in the source code,
//Processing objects that require deferred resolution Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName); if (result == null) { result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result;
The getLazyResolutionProxyIfNecessary() actually calls the getLazyResolutionProxyIfNecessary() method in the implementation class ContextAnnotationAutowireCandidateResolver. Let's see what's done in it,
@Override @Nullable public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null); } protected boolean isLazy(DependencyDescriptor descriptor) { for (Annotation ann : descriptor.getAnnotations()) { Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); if (lazy != null && lazy.value()) { return true; } } MethodParameter methodParam = descriptor.getMethodParameter(); if (methodParam != null) { Method method = methodParam.getMethod(); if (method == null || void.class == method.getReturnType()) { Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class); if (lazy != null && lazy.value()) { return true; } } } return false; } protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) { Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory, "BeanFactory needs to be a DefaultListableBeanFactory"); final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); TargetSource ts = new TargetSource() { @Override public Class<?> getTargetClass() { return descriptor.getDependencyType(); } @Override public boolean isStatic() { return false; } @Override public Object getTarget() { Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null); if (target == null) { Class<?> type = getTargetClass(); if (Map.class == type) { return Collections.EMPTY_MAP; } else if (List.class == type) { return Collections.EMPTY_LIST; } else if (Set.class == type || Collection.class == type) { return Collections.EMPTY_SET; } throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(), "Optional dependency not present for lazy injection point"); } return target; } @Override public void releaseTarget(Object target) { } }; ProxyFactory pf = new ProxyFactory(); pf.setTargetSource(ts); Class<?> dependencyType = descriptor.getDependencyType(); if (dependencyType.isInterface()) { pf.addInterface(dependencyType); } return pf.getProxy(beanFactory.getBeanClassLoader()); }
The code here should be easy to understand. First, it will judge whether the property Object is configured with @ Lazy annotation. If not, it will return null Object. Otherwise, it will call buildLazyResolutionProxy() method to create a proxy class for Lazy loading Object. In this proxy class, a custom implemented TargetSource is encapsulated, and a getTarget() method is overridden in this class, In the implementation of this method, we can see that the Object object is obtained by calling doResolveDependency(). Needless to say, we also know what it can do.
At this time, the returned BeanA proxy object is not null, so BeanB can also complete the instantiation, and then BeanA also completes the instantiation. But back to the main method, when printing BeanB, if you open debug, you will find that the attribute BeanA in BeanB is still a proxy class, but then print BeanB When getBeanA(), it is found that it is the reference address pointing to BeanA. In fact, the reference of the attribute BeanA in BeanB to the real BeanA instantiation is that when getBeanA() is called for the first time, it will call the getTarget() method of the previously customized encapsulated TargetSource, and finally get the instance of BeanA from the cache by calling the getBean() method.
Original design intention
It's also a deep understanding of the circular dependency in Spring, but I don't know if you've ever thought about whether the circular dependency error caused by parameter construction method injection or @ DependsOn annotation and setter injection really can't be solved and throw an exception? Or maybe that's why three-tier caching is designed to deal with circular dependencies?
In fact, everyone may have their own unique opinions, just as one thousand readers have one thousand Hamlets, and the only basis we can find is what the official Spring documents say,
original text
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.
The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.
Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.
Google translation
Since you can mix constructor based and setter based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that using the @ Required annotation on the setter method can be used to make a property a Required dependency; However, it is best to use constructor injection with parametric programming validation.
The Spring team usually advocates constructor injection because it allows you to implement application components as immutable objects and ensure that the required dependencies are not null In addition, constructor injected components are always returned to the client (calling) code in a fully initialized state. As a sidenote, a large number of constructor parameters are a bad code smell, which means that this class may have too many responsibilities and should be refactored to better solve the appropriate separation of concerns.
Setter injection should be used primarily for optional dependencies that can be assigned reasonable default values in the class. Otherwise, you must perform a non null check anywhere your code uses dependencies. One advantage of setter injection is that the setter method allows the objects of this class to be reconfigured or re injected at a later time. Therefore, managing through JMX MBean s is a compelling use case for setter injection.
Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes that you do not have a source, the choice is made for you. For example, if a third-party class does not expose any setter methods, constructor injection may be the only available form of DI.
summary
We know that the implementation logic of dependency injection is actually very complex, and circular dependency is an inevitable problem. In my opinion, combined with the IOC container, the three together form the whole of Bean instantiation in Spring, as shown in the following figure,
Understanding the implementation of circular dependencies and their solutions is the core of a deeper understanding of the Bean instantiation process in Spring. At the same time, Spring has many other important functions and implementations, such as AOP, BeanPostProcessor, Aware, etc. if beans are the heart, they are more like blood vessels around them, strengthening and expanding the overall functions of Spring.
To do one thing to the extreme is talent!