We all know that Spring uses level 3 caching to solve the problem of loop dependencies, but today I write an error. Level 3 caching is not turned off. In this article we will take a deeper look at level 3 caching and why my project failed to start.

1. What are circular dependencies?

The code!

@Service
class ServiceA {
    @Autowired
    ServiceB serviceB;
    
    public void test() {
    }
}
​
@Service
class ServiceB {
    @Autowired
    ServiceA serviceA;
    
    public void test() {
    }
}
Copy the code

To put it simply, there’s me in you, there’s you in me,

Of course, it doesn’t rule out that there’s a me, a her, and a you in this complicated, even more complicated relationship.

So cyclic dependence is a closed circular relationship.

2. What are the problems with circular dependencies?

Circular dependencies cause the object to be created in an infinite loop and cannot be created successfully.

At the time of instantiation object A, need to inject the object B, then we need to check whether there is in the cache object B, if the cache object B, then we need to instantiate the object B, began to instantiate the object B, the object B and need to rely on A, but the cache and not built A, also need to instantiate A, instantiation of A and B, B, and rely on A, It’s an endless loop, you can never successfully create A, B.

Instantiating object B first is the same as instantiating object A first.

3. How to solve the problem of circular dependencies?

On the basis of the characteristics of Java reference, we can be addressed by caching circular dependencies, we can build a half of the object in the cache, undertake steps at the bottom of the first, as below, after the completion of the steps to create the object nature of objects in the cache is full, because we just save reference address cache, when the object is created, Semi-finished objects become finished objects.

3.1 Level 1 Caching solves circular dependencies

We can also solve the problem of cyclic dependency only through level 1 cache, but there are many problems, not rigorous, for example, there are semi-finished objects in the cache, if our objects are not marked with the corresponding label, we may get semi-finished objects, causing some weird problems.

  • Not a good solution to the enhancement problem of using proxies (proxy objects are created ahead of time if necessary)
  • It is not clear which object has been built

3.2 Level 2 Cache solves the problem of loop dependency

Through the second level cache actually we can well solve the problem of circular dependencies, but we still couldn’t well solve the problem of enhancement, which use the proxy (when it is necessary to create a proxy object) in advance, by caching solve the problem of circular dependencies, requirements we injected object must be a proxy object, to achieve enhanced functionality.

Can we pre-initialize the proxy object only when a cyclic dependency occurs?

So when will we know that circular dependencies have occurred?

When we get objects from the second level cache during dependency injection, we can determine that a cyclic dependency has occurred, so we build proxy objects at this point.

We do not need to fetch the value from the second-level cache if there are no cyclic dependencies.

If we put an ObjectFactory in the secondary cache and create objects from the factory when the value is fetched from the secondary cache, wouldn’t that solve the cyclic dependency?

This seems to solve the problem of circular dependencies and create proxy objects when they occur, but a new problem arises.

New problem: If A depends on B and C, and B depends on A, and C depends on A.

If A is created first, A will be generated in level 2 cache when A is created, so it will be generated in level 2 cache when B is created, and it will be generated in level 2 cache when C is created

3.3 Level 3 Cache solves the problem of cyclic dependency

Spring provides level 3 caching by default to resolve cyclic dependencies

4. Spring three-level cache source code analysis

Here is the source code in Spring

Defining the level 3 cache

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

Get singleton, singleton mode (lazy-loaded double detection lock)

/** * Return the (raw) singleton object registered under the given name. * <p>Checks already instantiated singletons and  also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not  * @return the registered singleton object, or {@code null} if none found */ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock Object singletonObject =  this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }Copy the code

Enhance when creating beans

public interface ObjectFactory<T> { /** * Return an instance (possibly shared or independent) * of the object managed by  this factory. * @return the resulting instance * @throws BeansException in case of creation errors */ T getObject() throws BeansException; }Copy the code

GetObject will trigger AbstractAutowireCapableBeanFactory class createBean – > doCreateBean method

The Bean is created to determine whether level 3 caching is supported, which exposes objects in advance

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Copy the code

The getEarlyBeanReference method is used to obtain the proxy object. In fact, the underlying method is to generate the proxy object through the AbstractautoXyCreator class getEarlyBeanReference.

/** * Obtain a reference for early access to the specified bean, * typically for the purpose of resolving a circular reference. * @param beanName the name of the bean (for error handling purposes) * @param mbd the merged bean definition for the bean * @param bean the raw bean instance * @return the object to expose as bean reference */ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }Copy the code

In addition, when the object is created, it is checked (whether the second level cache is equal to the original object)

if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference ! = null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (! this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (! removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (! actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); }}}}Copy the code

5. Why does @async loop dependency not work

@async is also essentially enhanced by proxy mode

Injection failed during check, indicating inconsistency between the two proxy objects

This is the downside of premature injection. When using asynchronous annotations, spring’s default implementation is to generate a proxy object for the class from which asynchronous methods are called. That is, the proxy object is not a singleton, a previously injected proxy and a newly generated proxy, and Spring’s level 3 cache requires singleton objects for Bean property injection, which causes an error.

So, we can only solve this problem by breaking the loop dependency or optimizing the loading order of the class. If we create an object without @async first, no error will be reported

6. Solutions

  • Redesign to avoid circular dependencies and solve the problem of circular dependencies at the root
  • Use the @lazy annotation to lazily load
  • Specify the order of the load using the @dependson annotation
  • Change the file name, change the loading order of loop dependent classes (Spring recursively looks up the full file path, sort by path + file name, load the first one first)

Loop dependencies that need to be resolved themselves:

  • Multi-instance loop dependencies (not singleton pattern)
  • Constructor loop dependencies
  • @dependson creates a circular dependency
  • @async loop dependencies
  • .