Case Study of SpringBean Dependency and Level 3 Cache

  • 2021-08-31 07:45:21
  • OfStack

bean dependencies in spring can be broadly divided into two categories, with a total of three forms, which are briefly introduced below.

Class 1 is the circular dependency in the constructor, which will report an error


@Service
public class ServiceA { 
  private ServiceB serviceB; 
  public ServiceA(ServiceB serviceB) {
    this.serviceB = serviceB;
  } 
  public void methodA(){
    System.out.println("a");
  }
}
 
@Service
public class ServiceB { 
  private ServiceA serviceA; 
  public ServiceB(ServiceA serviceA) {
    this.serviceA = serviceA;
  } 
  public void methodB(){
    System.out.println("b");
  }
}
 
// Error prompt 
Description: 
The dependencies of some of the beans in the application context form a cycle:
 
 I-I-I-I 
| serviceA defined in file [C:\demo\target\classes\com\example\demo\ServiceA.class]
 ↑     ↓ 
| serviceB defined in file [C:\demo\target\classes\com\example\demo\ServiceB.class]
 I-I-I-I 

The second type is field circular dependency, which is divided into two types. The scope of scope of the first type circular dependency is singleton by default, and no error will be reported when it is started


@Service
public class ServiceA { 
  @Autowired
  private ServiceB serviceB;
 
  public void methodA(){
    System.out.println("a");
  }
}
 
@Service
public class ServiceB { 
  @Autowired
  private ServiceA serviceA;
 
  public void methodB(){
    System.out.println("b");
  }
}

The second scope, scope, is prototype. In this case, bean is multiple cases. It is reasonable to say that this startup will also report an error, but it succeeded. . I don't know why


@Service
@Scope("prototype")
public class ServiceA { 
  @Autowired
  private ServiceB serviceB;
 
  public void methodA(){
    System.out.println("a");
  }
}
 
@Service
@Scope("prototype")
public class ServiceB { 
  @Autowired
  private ServiceA serviceA;
 
  public void methodB(){
    System.out.println("b");
  }
}
 

According to the information I found on the Internet, spring can help us deal with the circular dependence of scope of bean and field of singleton. Personal feeling should be the same. Let's talk about its processing process below.

Simply talk about the loading process of bean under 1. When spring starts, beanDefinition is loaded into beanFactory first, and then whether it is sington and non-abstract class is not lazy will be judged according to beanDefinition, and then bean will be created.

The creation of bean is divided into three steps:

1. Call the constructor to create an object instance (after this step is completed, it can be referenced by other object instances)

2. Fill in the internal properties of the instance (the dependent bean will be fetched from the level 3 cache in turn, and if it is not found, the dependent bean will be created first, and then return to continue filling the properties)

3. Execute the initializeBean method to initialize

When bean is created, the getbean method is called first- > Execute the doGetBean method, The getSingleton method is called in the doGetBean method, This step is to get the object cache from the level 3 cache, because bean is just created, so it is definitely not in the cache, and then the createBean method will be called. In the createBean method, doCreateBean will be called to perform the creation process of bean, which is the above three steps. When bean is successfully created, it will be put into the level 1 cache, and it will be deleted from the level 3 and level 2 caches at this time.


public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
  }
 
  doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
 
    // Get an instance from the cache 
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
      // Omit code 
    } else {
      // Omit code 
      createBean(beanName, mbd, args);
    }
    // The completed bean Put in 1 Level cache 
    addSingleton(beanname , object)
  }
 
  protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    // Omit code 
    doCreateBean()
  }
 
  protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
    // Create an object instance according to the constructor 
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper.getWrappedInstance();
 
    // Place an object instance in the 3 Level cache 
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    // Instance internal property populating 
    populateBean(beanName, mbd, instanceWrapper);
 
    // Initialization bean
    exposedObject = initializeBean(beanName, exposedObject, mbd);
 
    return exposedObject;
  }
 
  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;
  }

We can see that the instance we just created will be put into singletonFactories (Level 3 cache) when Step 1 is completed, so let's understand what the Level 3 cache of spring is. The top cache is singletonObjects, in which the stored objects are bean that have been created and can be used normally. The second-level cache is called earlySingletonObjects, in which the stored bean is only instantiated by the construction method in the first step, without filling attributes and initialization. The third-level cache singletonFactories is a workshop.


/** 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 HashMap<>(16);

In fact, in the getSingleton method, bean will be obtained from level 1 cache first, and factory will be obtained from level 3 if level 1 cache does not obtain factory from level 2 cache. When factory is not null, getObject method will be called to obtain bean, and bean will be put into level 2 cache, and then the key-value pair will be deleted from level 3 cache. The code is as follows:


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;
 }

So here we can see why there can't be circular dependencies in constructors. If there are two service, a and b, They are cyclically dependent on each other in the constructor, When the project starts, When creating bean of a, perform the first step to create an instance through the construction method, but find bean that depends on b, so get it from level 3 cache, but don't find it, then suspend the creation process of a first, create b first, and rely on a during the creation process of b, but there is no bean of a in level 3 cache, so it enters a cycle creation process, which is naturally undesirable.

While the internal field scope is prototype, why will it report an error? When scope is prototype, a new object will be created every time it is referenced, so there will be a loop creation process.

By default, the scope of bean is singleton, and there is only one bean of the whole service in the whole container. If there is field cyclic dependence between a and b, when creating bean of a, after executing the construction method, the instance of a has been generated, and its factory object is stored in the third-level cache singletonFactories. When filling attributes, it is found that bean depends on b, but there is no b in the cache; Therefore, b is created instead. After executing the construction method of b in this process, factory is also put into level 3 cache. At this time, the attribute filling of b is executed, and it is found that it depends on a. Objects of a are obtained from level 3 cache, and a is put into level 2 cache, then intialize initialization is performed, and finally bean of b is transferred to level 1 cache. Then go back and create a. At this time, it is found that b is already in the level 1 cache, so the attribute is filled successfully, initialized, and finally a is also put into the level 1 cache, and the execution is finished.

Then you may wonder why you want to use Level 3 cache. I feel that it is ok to use Level 2 cache without singletonFactories.

From the previous code, you can see that what you put into the level 3 cache is an anonymous implementation class of objectFactory, addSingletonFactory (beanName, ()- > getEarlyBeanReference (beanName, mbd, bean), when we get objectFctory from singletonFactories and then call getObject method to get bean, it is actually object obtained through getEarlyBeanReference, so enter this method to see 1.

Here it gets all the beanPostProcessor implementation classes, Then find out the beanPostProcessor that implements SmartInstantiationAwareBeanPostProcessor, Then call its getEarlyBeanReference (obgect, beanName) method, process bean, and then return. These implementation classes have the core AbstractAutoProxyCreator of aop. From here, we can see that obejct obtained from the third-level cache objectFactory is a proxy object that has been processed. Personally, I understand that the third-level cache is to obtain some extended operations in advance in the process of creating objects.

Level 3 cache extends bean, Put the proxy object into a Level 2 cache, For other objects that depend on the bean, If there is no level 3 cache, If bean extension is implemented in level 2 cache, if bean a is dependent by other bean, bean a will be obtained many times in the process of filling attributes of other bean, and bean a proxy will be obtained many times in this process, which is redundant. However, level 3 cache structure is to complete bean extension in level 3 cache, generate proxy objects and put them into level 2 cache for other bean to obtain.


Related articles: