What is a cyclic dependency?
Circular dependency: During dependency injection, multiple Bean objects hold references to each other, such that object A contains object B, object B contains object A, and so on, so that they look like A circle, end to end.
You may be left with a question: What could go wrong with this situation?
So we need to think about why before we can solve a problem.
Circular dependencies in Spring
why
First of all, we need to combine the Spring Bean instantiation rules, and the previous article on the resolution of Spring dependency injection source content (if you are not clear about the DI part of Spring source code parsing), We already know that there are several cases where Spring instantiates its dependent Bean object before preinstantiating a Bean. So what happens when Spring preinstantiates a Bean object?
Do you notice something is wrong? They are like a loop recursion, unless there is a terminating condition, until the memory overflow exception.
Occurrence scenario and analysis
Now that we know the cause of the problem, the impatient friend may directly consider the Spring solution, but don’t worry, this is only a preliminary assumption, we have to analyze the different scenarios of the occurrence of cyclic dependency. There are cases where a Bean is instantiated before its dependent Bean object is instantiated. What are the cases? Next, let’s list and verify each of them.
First of all, we know that the scope of beans in Spring can actually be divided into three types: singleton, prototype, others (Request, Session, Application, WebSocket). In the source code of Bean instantiation, different instantiation strategies are selected according to the three scopes. Take a look at the source code (irrelevant code is temporarily omitted).
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 the Bean instance
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Clear the Bean instance
destroySingleton(beanName);
throwex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }// Create the prototype Bean object
else if (mbd.isPrototype()) {
Object prototypeInstance = null;
try {
// The default implementation registers beanName as the prototype Bean being created
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
// By default, remove the beanName of the prototype Bean that you just registered
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// Create a non-singleton, non-stereotype Bean
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
// If the scope is not configured, an invalid 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;
}
Copy the code
In contrast to singletons, which hold only one object, prototypes and others generate multiple objects. The following scenarios also verify singletons and multiple (prototype) cases separately.
Before we start, let’s introduce two properties that we’ll use:
/** Save beanName */ for 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 bea String, or Set
*/
private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation");
Copy the code
Parameter constructor 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(a) {
returnbeanB; }}@Component
public class BeanB {
@Autowired
private BeanA beanA;
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
public BeanA getBeanA(a) {
returnbeanA; }}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()); }}Copy the code
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?
Copy the code
There appears to be a cyclic dependency error in the constructor injection of the singleton. Let’s look at the prototype types and add them to the BeanA and BeanB classes, respectively
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Copy the code
Run the test code and find that the same error exception is thrown.
Now that we know the result, let’s first analyze the source code flow chart that generated the exception (here the source code will not be very fine, including the flow chart of the following scene, mainly focus on the point of direct importance, the chase for code details can be combined with the previous DI or Spring source code).
We will first before creating objects through isSingletonCurrentlyInCreation () or isPrototypeCurrentlyInCreation () method to judge whether the current need to create the Bean is created, If not will keep the current Bean beanName into singletonsCurrentlyInCreation or prototypesCurrentlyInCreation; Since we inject the constructor with a parameter (shown in yellow in the figure), the autowireConstructor() method is called, and the instantiation of the triggering constructor parameter BeanB occurs here. We know that its main function is to first determine the constructor parameters, and then determine the constructor through the parameters. The parameters are then converted to the desired type and finally instantiated with a call to Strategy.instantiate (). There is an important variable ArgumentsHolder inside the method, which encapsulates the arguments required by the constructor. Its creation is initialized by the call to createArgumentArray(),
private ArgumentsHolder createArgumentArray(
String beanName, RootBeanDefinition mbd, @NullableConstructorArgumentValues 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, newInjectionPoint(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);
}
Copy the code
Constructor parameter values are resolved by calling resolveDependency() for the autowiredArgument parameter.
@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 {
// Processes objects that require delayed resolution
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
descriptor, requestingBeanName);
if (result == null) {
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
returnresult; }}Copy the code
DoResolveDependency () : doResolveDependency() : doResolveDependency() : doResolveDependency() : doResolveDependency()
@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) {
returnshortcut; } Class<? > type = descriptor.getDependencyType(); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);if(value ! =null) {
if (value instanceofString) { 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);
}
/ / the 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); }}Copy the code
The resolveCandidate() method implements the resolution process, including the lookup of the Bean to determine the assembly. The key is the resolveCandidate() method.
public Object resolveCandidate(String beanName, Class
requiredType, BeanFactory beanFactory)
throws BeansException {
return beanFactory.getBean(beanName);
}
Copy the code
It turns out that the last thing it calls is the getBean() method, and this is where the actual instantiation triggers the dependent Bean. Thousands of times around, eventually returned to the origin.
The next step should be clear. The recursion will trigger the instantiation of BeanA in BeanB again. When BeanA is instantiated by calling getBean(), Judge isSingletonCurrentlyInCreation () or isPrototypeCurrentlyInCreation () method, is found BeanA had created, finally throw an exception.
throw new BeanCurrentlyInCreationException(beanName);
Copy the code
So whether singletons or multiple cases, this scenario of parameterized constructor injection (i.e., constructor injection in Spring) leads to circular dependencies that are unresolvable in Spring in general. But Spring does allow an unconventional way around this, the @lazy annotation, which we’ll talk about in more detail later.
No-parameter constructor method injection
Let’s look at the no-parameter constructor injection scenario, which is also called setter injection in Spring, and prepare the test class.
@Component
public class BeanA {
@Autowired
private BeanB beanB;
public BeanB getBeanB(a) {
return beanB;
}
public void setBeanB(BeanB beanB) {
this.beanB = beanB; }}@Component
public class BeanB {
@Autowired
private BeanA beanA;
public BeanA getBeanA(a) {
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()); }}Copy the code
Run the code and the console prints the following,
com.test.spring.entity.BeanA@47db50c5
com.test.spring.entity.BeanB@5c072e3f
com.test.spring.entity.BeanA@47db50c5
com.test.spring.entity.BeanB@5c072e3f
Copy the code
Constructor injection for singletons appears to be normal. Let’s look at the prototype types and add them to the BeanA and BeanB classes, respectively
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Copy the code
Run the test code and find that, unlike the singleton case, an error exception is thrown. It appears that the multi-instance constructor injection has a cyclic 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?
Copy the code
Unlike the parameter-based constructor injection, where singletons and prototypes produce different results, let’s first examine the source flow chart for each.
Here we need another directly related property, so let’s introduce it:
/** Save the ObjectFactory */ that created the Bean
private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16);
Copy the code
ObjectFactory is an ObjectFactory interface that returns an instance of an object. We often see it in source code as an anonymous inner class, such as:
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throwvar5; }});Copy the code
-
Singleton Singleton:
Instead of injecting with a parameter constructor, the @Autowired annotated property BeanA or BeanB triggers their instantiation during the property injection process, when the createBeanInstance() call has completed, indicating that the Bean object has been created. However, the initialization operation has not been completed. It is still in the process of instantiation. So if we 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); // Handle automatic assembly if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Automatic injection by name if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Automatic injection by type if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); booleanneedsDepCheck = (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 injectionapplyPropertyValues(beanName, mbd, bw, pvs); }}Copy the code
If you configure a Bean attribute dependency in XML, you will trigger a call to getBean() in autowireByName() or autowireByType() to complete the instantiation of the dependent Bean. Such as @autowired, @value, @resouce, etc. Here we are still in the test class @autowired, for example, in traverse BeanPostProcessor Bean object, will obtain the post processor AutoWiredAnnotationBeanPostProcessor, It will scan to configure the @autowired annotation property field, and then invokes the postProcessPropertyValues attributes () method on automatic injection, let’s take a look at postProcessPropertyValues (),
@Override public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException { // Get the metadata related to the autowire 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(); // Iterate over 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); }}}Copy the code
InjectedElement (target, beanName, PVS) InjectedElement (target, beanName, PVS) InjectedElement is an inner class of the metadata. And it has two subclasses: AutowiredFieldElement and AutowiredMethodElement, they rewrote the inject respectively () method, at the same time, they are also AutoWiredAnnotationBeanPostProcessor inner classes, AutowiredFieldElement is the annotation processing above the scan property field, AutowiredMethodElement is the annotation processing above the scan method parameter. The inject() method in AutowiredFieldElement is called by the test class.
@Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { // Get the property field Field field = (Field) this.member; Object value; // Determine whether to cache in the container if (this.cached) { // Get the 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 { // Resolve the specified dependencies according to the Bean definition in the container, 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 the dependent Bean 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 a reference to the dependent object and cache it this.cachedFieldValue = newShortcutDependencyDescriptor( desc, autowiredBeanName, field.getType()); }}}}else { this.cachedFieldValue = null; } this.cached = true; }}}if(value ! =null) { // Allow access through the JDK's reflection mechanism ReflectionUtils.makeAccessible(field); // Attribute field assignmentfield.set(bean, value); }}}Copy the code
Notice that the value is obtained by calling the template method resolveDependency(), which we have just examined in detail, in the same way as the resolveDependency() injected by the parameter constructor. If you look at the inject() method in the AutowiredMethodElement, you’ll find that the resolveDependency() method is used to retrieve the injected object.
So given that, it’s clear why singleton setter loop injection is normally allowed.
-
Prototype prototype:
BeforePrototypeCreation (beanName) is used to cache the beanName of the Bean being created. BeforePrototypeCreation (beanName) is called beforePrototypeCreation(beanName) before createBean() is called. And prototype and singleton is different in each request will create an object, and the cache is prototypesCurrentlyInCreation we already know the threads in a ThreadLocal isolation private variables, Spring is not allowed to create the prototype Bean when the current thread is already creating it, so the loop dependency exception is thrown.
@ DependsOn annotations
The @dependson annotation is a DependsOn annotation that is guaranteed to be created by the container before the dependent Bean.
Two more properties 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 the dependent Bean and the dependent Bean */
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
Copy the code
Next we prepare the test class as follows:
@Component
@DependsOn("beanB")
public class BeanA {
@Autowired
private BeanB beanB;
public BeanB getBeanB(a) {
returnbeanB; }}@Component
@DependsOn("beanA")
public class BeanB {
@Autowired
private BeanA beanA;
public BeanA getBeanA(a) {
returnbeanA; }}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()); }}Copy the code
Run the code and throw the following exception,
Circular depends-on relationship between 'beanB' and 'beanA'
Copy the code
There appears to be a cyclic dependency error in the constructor injection of the singleton. Let’s look at the prototype types and add them to the BeanA and BeanB classes, respectively
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Copy the code
Run the test code and find that the same error exception is thrown.
So let’s look at the source flow chart that generated the exception,
The process of finding the end of the exception is very short. Look at the source code. In fact, we will deal with the depenson dependency before choosing the instantiation strategy of different scopes.
// Get all the beans that the 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 + "'");
}
// Beans and dependent beans cache each other
registerDependentBean(dep, beanName);
// Create a dependency BeangetBean(dep); }}Copy the code
So regardless of the singleton prototype at this step, it already handles the loop dependency exception. This is consistent with the @dependson annotation in Spring.
The solution
After analyzing the specific source code handling of the above scenarios, you should have a general idea of how Spring handles circular dependencies and what is allowed and what is not allowed. In fact, the official Spring documentation has given us the answer (Google Translate, can directly check out the original).
Primary and secondary cache
And there are some very important property fields in the circular dependency handling code that we don’t talk about directly, but that apply to each of the above scenarios, and we’re going to list them here, including the ones that we’ve already covered,
/** Cache the instantiated singleton Bean */
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 creates Bean ObjectFactory */
private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16);
/** Cache beanName */ for 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 bea String, or Set
*/
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));
/** Cache beans and dependent beans */
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);
Copy the code
If classified, I think the first three are primary caches, from which Bean objects can be obtained directly, and the last five are secondary caches, which are used to record Bean state information during the instantiation process. Each of these plays an important role in verifying the concatenation of Spring’s cyclic dependencies to ensure the robustness of Bean instantiations in the container. We know that the getSingleton() method is called to try to get the Bean from the cache before instantiating the Bean. The role of the master cache is very important in this. The master cache uses three layers of caching, which are vertically layered according to the state of the Bean during the instantiation process.
@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;
}
Copy the code
The following is a flowchart showing the application of caching throughout the instantiation process,
I don’t know if you’ve ever thought about it, is it really unresolvable that there are cyclic dependency errors that are started with a parameter constructor injection or @dependson annotation versus a setter injection?
@ Lazy annotations
The previous analysis of cyclic dependency exceptions with parameter constructor injection left us with the buried point that the @lazy annotation can be used to solve the problem of exceptions. Let’s check the code,
@Component
public class BeanA {
@Autowired
private BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
public BeanB getBeanB(a) {
returnbeanB; }}@Component
public class BeanB {
@Autowired
private BeanA beanA;
public BeanB(@Lazy BeanA beanA) {
this.beanA = beanA;
}
public BeanA getBeanA(a) {
returnbeanA; }}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()); }}Copy the code
Run the code and it looks like this. It turns out to be 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
Copy the code
So what’s going on?
Back in our source code analysis, constructor arguments are resolved by calling the template method resolveDependency(), which contains the following code in the source code:
// Processes objects that require delayed resolution
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
if (result == null) {
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
return result;
Copy the code
And this getLazyResolutionProxyIfNecessary () is to realize the actual call the class ContextAnnotationAutowireCandidateResolver getLazyResolutionProxyIfNeces The sary() method, let’s see what’s going on 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
publicClass<? > getTargetClass() {return descriptor.getDependencyType();
}
@Override
public boolean isStatic(a) {
return false;
}
@Override
public Object getTarget(a) {
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 =newProxyFactory(); pf.setTargetSource(ts); Class<? > dependencyType = descriptor.getDependencyType();if (dependencyType.isInterface()) {
pf.addInterface(dependencyType);
}
return pf.getProxy(beanFactory.getBeanClassLoader());
}
Copy the code
The code here should be easy to understand. It first determines if the property object is configured with the @lazy annotation. If not, it returns null, otherwise it calls buildLazyResolutionProxy() to create a lazy-loaded proxy class. The proxy class wraps a custom implementation of the TargetSource. The class overwrites the getTarget() method, and the implementation of this method can see that the Object is captured by calling doResolveDependency(), so we know what it does.
BeanA’s proxy object is no longer null, so BeanB is instantiated, and BeanA is instantiated. But back inside the main method, if you turn debug on when printing BeanB, you will see that the property BeanA in BeanB is still a proxy class, but then print beanb.getBeana () and see that it is a reference to BeanA. BeanA gets a reference to the actual BeanA instantiation when getBeanA() is called for the first time. It calls the getTarget() method of the custom TargetSource. Finally, it calls the getBean() method. Get an instance of BeanA from the cache.
The design intent
This is a good way to know about cyclic dependencies in Spring, but have you ever wondered if a cyclic dependency error caused by a constructor injection or @dependson annotation is really unresolvable and throws an exception compared to a setter injection? Or maybe that’s why the three-tier cache is designed to deal with circular dependencies?
In fact, everyone may have their own unique opinions, just like a thousand readers have a thousand Hamlets, and the only basis we can find is probably the official documentation of Spring says,
The original
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 use a mix of construction-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 the @Required annotation on the setter method can be used to make the property a Required dependency; However, it is better to use constructor injection with parameter programming validation.
The Spring team often advocates constructor injection because it lets you implement application components as immutable objects and ensures 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 side note, a large number of constructor arguments is a bad code smell, which means that the class may have too much responsibility and should be refactored to better address a proper separation of concerns.
Setter injection should be used primarily only for optional dependencies that can be assigned a reasonable default value in the class. Otherwise, you must perform a non-empty check anywhere your code uses a dependency. One benefit of setter injection is that setter methods make objects of the class reconfigurable or reinjected at a later date. Thus, management through JMX MBeans 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 from which you have no 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 form of DI available.
conclusion
As we know, the implementation logic of dependency injection is very complicated, and circular dependency is an inevitable problem. In my opinion, combined with the IOC container, the three of them together constitute the whole of Bean instantiation in Spring, as shown in the following figure.
Understanding circular dependencies and the implementation of their resolution is the core of a deeper understanding of the Bean instantiation process in Spring. At the same time, Spring also has many other important functions and implementations, such as AOP, BeanPostProcessor, Aware, etc. If beans are the heart, they are more like the blood vessels around, strengthening and expanding the overall functions of Spring.
To do one thing extremely is talent!