spring ioc: circular dependency, L3 cache and FactoryBean

Last This article introduces the spring ioc container initialization process as a whole (starter - > refresh - > registerbeandefinition - > docreatebean - > populatebean)

But it is limited to the routine process. How to deal with some special cases, such as "object loop application" spring - this is the question to be answered in this article.

1. How does spring solve the problem of circular dependency of objects?

be careful:
Constructor type circular dependency, spring will throw exceptions directly
The focus here is on the circular dependency of attribute injection

What is circular dependency?

@Component
class A{
    @Autowire
    B b;
}

@Component
class B{
    @Autowire
    A a;
}

The above code is A relatively simple circular dependency: while A refers to B, B also refers to A

What problems will you encounter during initialization in this case?
When you create a, you find that B is referenced, and you create B instead; When creating B, I found that I referenced A and instead created a, resulting in an endless loop

How?

The solution is also simple. Just declare a set (e.g. creatingSet). When you need to create an object, check whether there is one in the set, and put the object into the set.

In this way, the initialization process of objects A and B becomes:

  1. Check whether there is object a in the creatingSet. If not, create a and record the creatingSet set(A)
  2. It is found that A refers to B and creates B instead. There is no object B in the creatingSet. Create and record the creatingSet set(B)
  3. It is found that B refers to A. at this time, there is object a in the creatingSet, so there is no need to create a direct assignment

spring is the same idea. It records the name of the object being created through the singletons currentlyincreation collection, and uses the three-level cache to store the object.

How does spring solve it?

Take a simpler example of circular dependency:

@Component
class A{
    @Autowire
    A a1;
}

Let's look at the initialization process of A object

getBean(beanName){
    // -- 1. Add a flag [singletonsCurrentlyInCreation.set(beanName)]
    beforeSingletonCreation(beanName);

    // -- 2. Constructor creates object [a object is created, and its attribute a1 is empty]
    instanceWrapper = createBeanInstance(beanName, mbd, args);

    // -- 3. Put it into L3 cache and remove L2 [factories with a1 in L3 cache]
    // ##Only circular dependency can be executed here, and the agent action is advanced
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

    // -- 4. Property initialization, recursive call getSingleton(String,boolean)
    // [initialization attribute a1 start]
    populateBean();
       ⬇⬇⬇⬇⬇
    Object getSingleton(String beanName, boolean allowEarlyReference)
      Object getSingleton(String beanName, boolean allowEarlyReference) {
       // ==a. try to get from L1 cache
       Object singletonObject = this.singletonObjects.get(beanName);
       // ==b. L1 cache is not & & but is being created (if will be entered during recursion)
       if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
           // **Factory creation obtained through L3 cache
           singletonObject = singletonFactory.getObject();
           // Add level 2 and remove Level 3
           this.earlySingletonObjects.put(beanName, singletonObject);
           this.singletonFactories.remove(beanName);
       }
       // ==c. during recursion, execute the if of code 2 and return the L2 cache object
       return singletonObject;
    }
      ⬆⬆⬆⬆⬆
    // [end of initialization attribute a1: a1 object exists in L2 cache, and a1 factory of L3 cache is removed]
    populateBean(); 

    // ##4.5 post processor of calling object [code 3 has been used as an agent in advance, so it will not be executed here]
    // ## initializeBean(beanName, exposedObject, mbd)

    // -- 5. Inject attribute [a1 attribute of object a has value]
    applyPropertyValues(beanName, mbd, bw, pvs);

    // -- 6. Remove the tag [singletonsCurrentlyInCreation.remove(beanName)]
    afterSingletonCreation(beanName);

    // -- 7. Remove Level 3 and level 2, and store the object in level 1 cache [sprinkle flowers after completion, and initialize object A]
    addSingleton(beanName, singletonObject);

Code 3, code 4 and code 4.5 have changed.

The above is how spring solves the problem of circular application of objects.

2. Why does spring design two-level and three-level caches to solve circular dependency?

In fact, this problem can be divided into two problems.

A. Using L2 cache only: is it feasible to create proxy objects directly from code 3 locations?

A: Yes, but it doesn't conform to the concept of spring.

spring's idea is that Bean completes the proxy at the last step of the life cycle, rather than completing the proxy immediately after instantiation.

In general, there are two ways to solve circular dependency:

  1. Whether there is circular dependency or not, create the proxy object in advance and put the proxy object into the cache; When circular dependency occurs, it is obtained directly from the cache.
    The problem discussed in this section -- using only L2 cache: creating proxy objects directly from code 3 locations -- is this way.
    Even if it's a little rougher, the L2 cache is saved. Putting it into the L1 cache can also solve the problem. (you think, in the end, these objects will be loaded anyway)
  1. The proxy object is not created in advance, and the proxy object is generated only when circular dependency occurs.
    Obviously, spring chose this way.

B. Is it feasible to use only L3 cache and create it through the factory every time?

A: No.

This is easy to understand. Every time an object is created through the factory, the singleton will be destroyed.

3. What is FactoryBean?

In addition to ordinary objects, spring also provides users with a FactoryBean interface to create complex objects. (regarded as an extension point)

Extension point

If a FactoryBean object is injected into spring, what you get from SpringContext is a call to FactoryBean A normal object created by getobject().

A little tongue twister, for example:

// Declare a Test object
class Test{}

// Declare a TestFactoryBean object to implement the FactoryBean interface
class TestFactoryBean implements FactoryBean<Test> {
    @Override
    public CostForm getObject() throws Exception {
        Test test = new Test();
        return test;
    }

    @Override
    public Class<?> getObjectType() {
        return Test.class;
    }
}

In the above example, through springcontextutil The object obtained by getBean ("TestFactoryBean") is not TestFactoryBean, but Test.

So how to get TestFactoryBean? Just add "&" prefix to name.

TestFactoryBean factoryBean = SpringContextUtil.getBean("&testFactoryBean")
Test test = SpringContextUtil.getBean("testFactoryBean");

Source code implementation

Why? Find the answer through the source code.

  • Injection of TestFactoryBean object
// Object initialization has such a logic
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
preInstantiateSingletons(){
    // If it is a FactoryBean object, the "&" prefix injection will be added
    if (isFactoryBean(beanName)) {
          Object bean = getBean("&" + beanName);
    }
}
  • Injection of Test object

Spring's execution entry for FactoryBean object processing is here:

// Object initialization logic previously analyzed
sharedInstance = getSingleton(beanName, () -> {
            return createBean(beanName, mbd, args);        
        });
// ##After the object is created, special processing is performed
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

Observe the specific treatment:

Object getObjectForBeanInstance(
            Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
    // --Non factrybean object, return directly
    if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
            return beanInstance;
    }

    // --Get objects through FactoryBean
    FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
    object = getObjectFromFactoryBean(factory, beanName, !synthetic);
        ⬇⬇⬇⬇⬇⬇
        // ##This will eventually be called to execute the getObject method of the FatoryBean
        object = factory.getObject();
        ⬆⬆⬆⬆⬆⬆
    object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}

appendix

P6-P7 knowledge collection

Keywords: Java Spring

Added by sherri on Thu, 24 Feb 2022 08:29:40 +0200