Pay attention to “Su SAN said technology”, reply: development manual, time management surprise.

For those of you who may not be familiar with spring’s loop dependency issues, let’s take a look at this example.

@Service public class AService { private BService bService; public AService(BService bService) { this.bService = bService; } public void doA() { System.out.println("call doA"); }}Copy the code
@Service public class BService { private AService aService; public BService(AService aService) { this.aService = aService; } public void doB() { System.out.println("call doB"); }}Copy the code
@RequestMapping("/test") @RestController public class TestController { @Autowired private AService aService; @RequestMapping("/doSameThing") public String doSameThing() { aService.doA(); return "success"; }}Copy the code
Public static void main(String[]); public static void main(String[]); args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}Copy the code

We reported the following exception when running the main method of the Application class to start the service:

Requested bean is currently in creation: Is there an unresolvable circular reference?
Copy the code

It is obvious here that circular dependencies have occurred.

What are circular dependencies?

Cyclic dependencies are instances A dependent on instance B, which in turn depends on instance A.

Or instance A depends on instance B, and instance B depends on instance C, and instance C depends on instance A.

Such interdependencies between instances that form a loop are circular dependencies.

Why circular dependencies?

In the example above, the constructor public AService(BService BService) is called when the AService is instantiated. The constructor depends on the instance of BService. The constructor public BService(AService AService) needs to be called to complete the instantiation. This constructor also needs the instance of AService as a parameter. AService and BService are not instantiated in advance, and they rely on each other’s instances as parameters during the instantiation process. This creates an endless cycle, so they can no longer be instantiated.

How does Spring address loop dependencies?

Just tweak the above example to use Autowired injection instead of constructor injection.

@Service public class AService { @Autowired private BService bService; public AService() { } public void doA() { System.out.println("call doA"); }}Copy the code
@Service public class BService { @Autowired private AService aService; public BService() { } public void doB() { System.out.println("call doB"); }}Copy the code

We see that it starts normally, indicating that the loop dependency has resolved itself

Why can Spring cycle dependencies?

Call the applicationContext. GetBean (xx) method, will eventually be transferred to the AbstractBeanFactory doGetBean method of a class. Because this method is very long, I have omitted some irrelevant code.

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; Object sharedInstance = getSingleton(beanName); if (sharedInstance ! = null && args == null) {omit........ bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else {omit........ if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); }}); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new 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); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; }} omit........ return (T) bean; }Copy the code

As you can see, the method will call the getSingleton method to get the instance from the cache, if not. It determines whether the scope is singleton, multi-column, or none. The rules for creating instances are different for different scopes. Next, let’s focus on the getSingleton method.

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

We find that there are three Map sets:

/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<? >> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);Copy the code

SingletonObjects corresponds to the first-level cache, earlySingletonObjects corresponds to the second-level cache, and singletonFactories corresponds to the third-level cache.

The logic of the getSingleton method above is as follows:

First fetch the instance from singletonObjects (level 1 cache), then return the singletonObject instance if it is available.

If you can’t get the instance from singletonObjects (level 1 cache), then you can get the instance from earlySingletonObjects (level 2 cache). If you can get the instance from singletonObjects (level 2 cache), you can return singletonObject.

If you can’t get an instance from earlySingletonObjects, then you can get a singletonFactory from singletonFactories. If you can get a singletonFactory, then you can call getObject to create an instance. Put the created instance into earlySingletonObjects (level 2 cache), remove the singletonFactory instance from The singletonFactories (level 3 cache), and return the singletonObject instance.

If you can’t get an instance from singletonObjects, earlySingletonObjects, or singletonFactories, the singletonObject object is empty.

For instance you need to call applicationContext. GetBean (” XXX “) method, the first call to getBean method, code in getSingleton method returns singletonObject object is empty. By default, beans are scoped singletons. Let’s focus on this code:

The createBean method calls the doCreateBean method, which is also longer, so we omit the irrelevant code.

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; Omit... if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); Omit... boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) {omit..... } to omit... return exposedObject; }Copy the code

The main process of this method is as follows:

Creating a bean instance

Determine if the scope is a singleton, allows loop dependencies, and the bean is currently being created and has not yet been created. If all conditions are met, addSingletonFactory is called to put the bean instance into the cache.

Call the populateBean method for dependency injection

Call the initializeBean method for object initialization and AOP enhancement

We can focus on the addSingletonFactory method first.

  protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
        this.singletonFactories.put(beanName, singletonFactory);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
      }
    }
  }
Copy the code

The logic of this method is to determine if the singletonObjects (level 1 cache) can’t find an instance, then put the singletonFactory instance into the singletonFactories (level 3 cache), And remove the instance in earlySingletonObjects (level 2 cache).

After executing the createBean method, the outer getSingleton method is called

Let’s focus on the getSingleton method

public Object getSingleton(String beanName, ObjectFactory<? > singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!) "); } beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; }}Copy the code

Logic is simple, the method is first from singletonObjects (l1), for instance, if you do not get to call the singletonFactory. GetObject () method to create an instance, The addSingleton method is then called into the singletonObjects cache.

if (earlySingletonExposure) {
    addSingletonFactory(beanName, () ->       getEarlyBeanReference(beanName, mbd, bean));
}

Copy the code

The getObject method will eventually call the getEarlyBeanReference method

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }Copy the code

This method allows entity beans to generate proxy objects.

Look again at the addSingleton method

protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }}Copy the code

This method puts the instance into singletonObjects (level 1 cache) and removes the singletonFactories (level 2 cache) so that any future calls to getBean can get the instance from singletonObjects (level 1 cache).

Having said that, let’s go back to the scenario in the example.

Why does Spring use level 3 cache instead of level 2 cache?

In cases like the example, using only the second level cache is fine.

But suppose instance A depends on both instance B and instance C, and instance B depends on instance A, and instance C depends on instance A.

When a is instantiated, objectFactorya is exposed to level 3 cache in advance, and getBean(b) is called to dependency inject THE INSTANCE of B. After instantiation of B, objectFactoryb is exposed to level 3 cache in advance, and getBean(a) is called to dependency inject instance A. As objectFactorya is exposed in advance, instance A can be obtained from level 3 cache at this time, and instance B completes dependency injection and is upgraded to level 1 cache. After c is instantiated, objectFactoryc is exposed to level 3 cache in advance, and getBean(a) is called to dependency inject instance A. As objectFactorya is exposed in advance, instance A can be obtained from level 3 cache at this time. Note here again to obtain a instance from the l3 cache, we know that level 3 is in the cache instance by calling singletonFactory. GetObject () method to obtain, and return the result might be different every time. If you don’t use the second level cache, there’s a problem, because you get different instances of A.

In addition, if there is no third level cache, you don’t have singletonFactory. GetObject () method, the object cannot generate extensions, certainly generate a proxy object is one way to extend.

Conclusion:

Loop dependencies can only be resolved in singletons, and allowCircularReferences should be set to true.Copy the code

Circular dependencies still occur in the following cases:

Constructor injection

In cases where the scope is not a singleton, of course, in a custom scope, you can implement your own logic to avoid loop dependencies

The allowCircularReferences parameter is set to false

If you like this article, please pay attention to it: Su SAN said technology