What are circular dependencies?
As the name suggests, A cyclic dependency is when A depends on B, which in turn depends on A, and the dependence between the two forms A circle, usually due to incorrect coding. Spring can only solve the property loop dependency problem, not the constructor loop dependency problem, because there is no solution.
Let’s start by writing a Demo to show how Spring handles property loop dependencies.
Talk is cheap. Show me the code
Step 1: Define a class ComponentA that has a private property componentB.
package com.tech.ioc; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @component public class ComponentA {@autowired private ComponentB ComponentB; public void say(){ componentB.say(); }}Copy the code
Step 2: Define a class ComponentB that depends on ComponentA. And define a say method for printing data.
package com.tech.ioc; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @component public class ComponentB {@autowired private ComponentA ComponentA; public void say(){ System.out.println("componentA field " + componentA); System.out.println(this.getClass().getName() + " -----> say()"); }}Copy the code
Step 3: Focus, write a class -SimpleContainer – that mimics Spring’s low-level handling of loop dependencies. If you understand this code, it’s easy to look at Spring’s logic for handling loop dependencies.
package com.tech.ioc; import java.beans.Introspector; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Shows how cyclic dependencies are handled in Spring. This is a simplified version of how real Spring dependencies are handled. * But the general idea is the same. In addition, this Demo does not consider many situations, such as thread safety, for reference only. * @author junzhan ** **/ public class SimpleContainer {/*** * */ Private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); /*** * is used to store newly created beans whose properties have not been processed, so beans stored in this cache are not yet available. */ Private final Map<String, Object> singletonFactories = new HashMap<>(16); public static void main(String[] args) { SimpleContainer container = new SimpleContainer(); ComponentA componentA = container.getBean(ComponentA.class); componentA.say(); } public <T> T getBean(Class<T> beanClass) { String beanName = this.getBeanName(beanClass); Object Bean = this.getsingleton (beanName); If (bean == null) {return createBean(beanClass, beanName); } return (T) bean; } /*** * get Bean instances based on beanName from level 1 and level 2 caches, Possible empty * */ private Object getSingleton(String beanName) {// First try to get Object instance = from level 1 cache singletonObjects.get(beanName); SingletonFactories instance = singletonFactories. Get (beanName); } return instance; } /*** * create an instance of the specified Class, * * */ private <T> T createBean(Class<T> beanClass, String beanName) { try { Constructor<T> constructor = beanClass.getDeclaredConstructor(); T instance = constructor.newInstance(); SingletonFactories. Put (beanName, instance); Field[] fields = beanClass.getDeclaredFields(); for (Field field : fields) { Class<? > fieldType = field.getType(); field.setAccessible(true); ComponentB = ComponentB; // ComponentB = ComponentB; // ComponentB = ComponentB ComponentB.com ComponentB: ComponentB.com ComponentB: ComponentB.com ComponentB: ComponentB.com ComponentB: ComponentB.com ComponentB: ComponentB.com // Because we put the ComponentA instance in the level 3 cache earlier, it can be found. ComponentB = ComponentB; // ComponentB = ComponentB; // ComponentB = ComponentB; field.set(instance, this.getBean(fieldType)); } // Finally, the initialized Bean is set to level 1 cache. singletonObjects.put(beanName, instance); return instance; } catch (Exception e) { e.printStackTrace(); } throw new IllegalArgumentException(); } /** * The class name is lowercase as beanName, Spring's underlying implementation is similar to this, * {@linkplain Introspector#decapitalize(String)} **/ private String getBeanName(Class<? > clazz) { String clazzName = clazz.getName(); int index = clazzName.lastIndexOf("."); String className = clazzName.substring(index); return Introspector.decapitalize(className); }}Copy the code
If you have read and understood the above code, then we will proceed to the actual Spring processing loop dependency problem source analysis, I believe it will be easy to read again.
Underlying source code analysis
The analysis starts with the AbstractBeanFactory doGetBean method. As you can see, the method first calls transformedBeanName, which does exactly the same thing as our own getBeanName method, but Spring is far more complicated than that because of FactoryBean and alias issues.
// AbstractBeanFactory#doGetBean protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); Object bean; / /!!!!!! The important point here is that we first get the corresponding Bean from the cache beanName. Object sharedInstance = getSingleton(beanName); if (sharedInstance ! = null &&args == null) {// The Bean instance specified by beanName exists in the cache. Bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); Else {try {// Delete code irrelevant to this analysis.... // If it is a singleton Bean, If (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; }}); } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } return (T) bean; }Copy the code
The overloaded getSingleton method is called. Note that the Boolean parameter passed here is true, because it determines whether the early Bean is allowed to be exposed.
// DefaultSingletonBeanRegistry#getSingleton public Object getSingleton(String beanName) { return getSingleton(beanName, true); }Copy the code
// DefaultSingletonBeanRegistry#getSingleton protected Object getSingleton(String beanName, Boolean allowEarlyReference) {/ / from the first level cache access Object singletonObject = this. SingletonObjects. Get (beanName); If (singletonObject = = null && isSingletonCurrentlyInCreation (beanName)) {/ / if not get in the primary cache, Again from the second level cache for singletonObject = this. EarlySingletonObjects. Get (beanName); If (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) { //Double Check singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); If (singletonObject == null) {// Finally try to get ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) { singletonObject = singletonFactory.getObject(); / / save to the second level cache this. EarlySingletonObjects. Put (beanName singletonObject); / / removed from the l3 cache enclosing singletonFactories. Remove (beanName); } } } } } } return singletonObject; }Copy the code
Ok, after seeing how Spring fetches Bean instances from the cache, what about how the creatBean method creates beans
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {// Delete code irrelevant to this analysis... The try {// createBean method underlies the creation of the Bean by calling doCreateBean. Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); }}Copy the code
// AbstractAutowireCapableBeanFactory#doCreateBean protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) {// createBeanInstance instanceWrapper = createBeanInstance(beanName, MBD, args); } Object bean = instanceWrapper.getWrappedInstance(); // If the current Bean is allowed early exposure. As long as the Bean isSingleton and the allowCircularReferences attribute is true(the default is true) Boolean earlySingletonExposure = (mbd.issingleton () && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); If (earlySingletonExposure) {// The addSingletonFactory method is called to save the newly created Bean to the level 3 cache. addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Remove the code that is not relevant to this analysis..... Object exposedObject = bean; Try {// The Bean property fills the populateBean(beanName, MBD, instanceWrapper); // Initialize the Bean, known as the Aware interface, InitializingBean interface..... This is where exposedObject = initializeBean(beanName, exposedObject, MBD) is called; } catch (Throwable ex) {} // Delete code not relevant to this analysis..... return exposedObject; }Copy the code
Start with the addSingletonFactory method, where the Bean is stored in a level 3 cache.
protected void addSingletonFactory(String beanName, ObjectFactory<? > singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); Synchronized (this.singletonObjects) {// Synchronized (this.singletonObjects) {// Synchronized (this.singletonObjects) { This. SingletonObjects. Either containsKey (beanName)) {/ / will just create good Bean sample save to triple the cache this. SingletonFactories. Put (beanName, singletonFactory); // Remove from level 2 cache. this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }}}Copy the code
The dependency injection for processing beans is done by the populateBean method, but the entire execution link is too long to be covered here. Instead, we will describe how the IoC container calls the getBean method step by step to handle dependencies, which will correspond to our own logic for handling field injection.
Protected void populateBean(String beanName, RootBeanDefinition MBD, @nullable BeanWrapper bw) {// Delete code that is not related to this analysis... PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } / / to iterate through all the registered BeanPostProcessor interface implementation class, if the implementation class is InstantiationAwareBeanPostProcessor interface types, call its postProcessProperties method. for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); // Delete code that is not relevant to this analysis... pvs = pvsToUse; }} // Delete code that is not relevant to this analysis... }}Copy the code
In Spring, the @autowired annotation by AutowiredAnnotationBeanPostProcessor class to handle, and @ Resource annotation is handled by CommonAnnotationBeanPostProcessor class, These two classes are realized InstantiationAwareBeanPostProcessor interface, are completed in overwrite postProcessProperties method dependency injection. Here we look at the handling of @autowired annotations.
// AutowiredAnnotationBeanPostProcessor#postProcessProperties public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {/ / according to beanName and bean class to look up the bean's reliance on metadata - InjectionMetadata InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); 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; }Copy the code
InjectedElement (InjectedElement); InjectedElement (InjectedElement); InjectedElement (InjectedElement);
// InjectionMetadata#inject public void inject(Object target, @Nullable String beanName, @nullable PropertyValues PVS) throws Throwable {// Get all the dependency injection elements of the current Bean. Collection<InjectedElement> checkedElements = this. CheckedElements; Collection<InjectedElement> elementsToIterate = (checkedElements ! = null ? checkedElements : this.injectedElements); if (! ElementsToIterate. IsEmpty ()) {/ / if the current Bean dependency injection is not null, traverse the dependency injection element for (InjectedElement element: ElementsToIterate) {// Call the Inject method of each dependency injection element. element.inject(target, beanName, pvs); }}}Copy the code
AutowiredAnnotationBeanPostProcessor class defines the two inner classes – AutowiredFieldElement, AutowiredMethodElement inherited from InjectedElement, They correspond to field injection and method injection respectively.
Take the commonly used field injection as an example. In the Inject method of AutowiredFieldElement, first determine whether the current field has been processed. If it has been processed, directly remove the cache; otherwise, call resolveDependency method of BeanFactory to deal with the dependency.
// AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Field field = (Field) this.member; Object value; If (this.cached) {// If the current field has already been processed, fetch value directly from the cache = resolvedCachedArgument(beanName, this.cachedfieldValue); } else {// construct DependencyDescriptor descriptor 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 {/ / call the BeanFactory resolveDependency to parse the dependent value = the BeanFactory. ResolveDependency (desc, beanName autowiredBeanNames, typeConverter); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); } // Remove code irrelevant to this analysis.... } if (value ! = null) {/ / through the reflection to the attribute assignment ReflectionUtils. MakeAccessible (field); field.set(bean, value); }}}Copy the code
In DefaultListableBeanFactory resolveDependency method, finally call doResolveDependency method to complete depend on analytic function. In Spring source code, if there is a do method, that method is the one that does the real work.
// DefaultListableBeanFactory#resolveDependency public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { // ..... // If you add @lazy annotations to fields (methods), So here will not be real to resolve depend on the Object result = getAutowireCandidateResolver () getLazyResolutionProxyIfNecessary (descriptor, requestingBeanName); If (result == null) {// If @lazy is added, Call the doResolveDependency method to resolve dependencies result = doResolveDependency(Descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; }Copy the code
// DefaultListableBeanFactory#doResolveDependency public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { //..... Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, Descriptor); If (matchingbeans.isempty ()) {// if the dependency is not found if (isRequired(descriptor)) {// if the dependency isRequired(@aautowired's required attribute), Direct throw an exception raiseNoMatchingBeanFound (type, descriptor. GetResolvableType (), descriptor); } return null; } String autowiredBeanName; Object instanceCandidate; // If more than one dependency is found, for example, there are multiple implementation classes for an interface that are registered with the IoC container. If (matchingBeans.size() > 1) {// Decide which implementation class to use, @ Primary methods such as is done here autowiredBeanName = determineAutowireCandidate (matchingBeans, descriptor); if (autowiredBeanName == null) { if (isRequired(descriptor) || ! indicatesMultipleBeans(type)) { return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans); } else { 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); } // If the dependency found is the Class of a Class(as is usually the case), instead of an instance, // call the descriptor's method to get an instance of the type resolveCandidate. if (instanceCandidate instanceof Class) { instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); } / /... }Copy the code
In the resolveCandidate method, the dependent Bean instance is obtained by calling the getBean method of the BeanFactory.
// DependencyDescriptor#resolveCandidate public Object resolveCandidate(String beanName, Class<? > requiredType, BeanFactory beanFactory) throws BeansException { return beanFactory.getBean(beanName); }Copy the code
In the getBean method implementation, it is still done by calling the doGetBean method. This is basically the same as the dependency handling we wrote ourselves, except that our own dependency handling is simpler, whereas Spring has more complex scenarios to consider and handle, so the code is more complex, but the general idea is the same.
// AbstractBeanFactory#getBean
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
Copy the code
The point is that the previous Demo we wrote dealing with loop dependencies is very simple if you understand that code and look at Spring’s loop dependencies.
conclusion
A cyclic dependency is when two beans reference each other, for example, A depends on B, and B depends on A. Spring can only solve property cyclic dependencies, not constructor cyclic dependencies, and this scenario is not solved either.
The key to Spring’s solution to cyclic dependencies is to store the Bean in the level 3 cache when dealing with the Bean’s property dependencies. When a cyclic dependency exists, the Bean is retrieved from the level 3 cache, removed from the level 3 cache, and stored in the level 2 cache. Finally click here to receive the Java architecture package!!