This is the 18th day of my participation in the August Genwen Challenge.More challenges in August

This is a summary of the three previous articles on circular dependencies

3.1 Spring 5 source code series – cyclic dependencies handwritten code to simulate spring cyclic dependencies

Chapter 2:3.2 Spring source code series —- loop dependency source analysis

Chapter 3:3.3 Spring5 source code – the final solution for Spring to read incomplete beans in the loop dependency process

Now summarize the idea of circular dependencies

I’ve learned so much, but I’ve seen so much, and I know how others have solved a certain kind of problem, and that’s the magic of good code. That’s why it’s important to learn other people’s code.

Ideas are what we can use in our work

  1. Cyclic – dependent three-level cache design

  2. The interface function


A loop – dependent three-level cache design

The recirculation dependency process is designed with three levels of caching, respectively

  1. Level 1 cache: Used to store complete beans

  2. Level 2 cache: Used to store early, clean beans

  3. Level 3 cache: Used to store interface functions.

/** 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. */ ** * level cache The map user cache key is beanName, Value is objectFactory(wrapped as an early object) */ private final Map<String, objectFactory <? >> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: Bean name to bean instance. */ ** * Secondary cache, user cache our key is beanName, value is our early object. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);Copy the code

 

If you think about it, all three caches are very necessary.

1.1 Introducing Level-1 Cache

At first, there is only level 1 caching, and after the entire bean is created, its entire bean is put into the level 1 cache. What’s wrong with that?

  1. There are three main steps in bean creation (instantiation, property assignment, and initialization). While the first thread is creating the bean, another thread comes along, finds that the level 1 cache is not there yet, and goes back to create it again. Doesn’t that just repeat itself? Ioc requires that beans be singletons.

  2. Lock it. Can lock it solve this problem? Yes, but it’s super inefficient. Lock the level 1 cache, and all object creation will wait. Even if they’ve already created it. It is unacceptably inefficient

  3. This is where the second level cache is introduced.

1.2 Introducing Level-2 Cache

The introduction of the second level cache can solve the problem that the link of the bean created by the first level cache is too long. Once the bean is created, it is immediately put into the second level cache. After the entire bean is created, the second level cache is removed before it is put into the first level cache. Doing so solves the problem of multi-threaded bean creation. Shorten the entire link. At the same time, the bean is fetched from the cache each time, and returned if it is already in the level 1 cache. You don’t have to execute the subsequent creation code

So what’s wrong with level 2 caching?

There is one more thing you need to know about dynamic proxy creation beans. When to use dynamic proxy creation beans? We usually say that after initialization, we call the bean’s post-processor to create the bean. This is just when most beans create dynamic proxies. What about circular dependencies? It has cyclic dependencies and is created too late after initialization. This needs to be created after instantiation. Thus, the code for the dynamic proxy is coupled to the creation bean. Violate the principle of uniformity.

So, a three-level cache was introduced

1.3 Introducing Level-3 Caching

The three-level cache was introduced to solve the coupling problem. Make each method do only one thing. Clever use of interface functions.

What does this interface function do? This is equivalent to the js callback function. I defined it up front, but I don’t do it. Do not execute until the conditions are met. This method can be applied to practical work on a large scale.

For example, call a dynamic proxy to create a bean. As soon as the instantiation is complete, I give you the ability to call a dynamic proxy. But will you actually be able to use it later? Not necessarily. You can only use this ability if you meet the conditions.

Define interface functions, also known as hook functions

In loop-dependent source code, interface functions are used twice.

The first is when the bean is created. The second is level 3 caching

Let’s look at the source code,

The first time: When creating the bean, define a hook function createBean().

SharedInstance = getSingleton(beanName, () -> {try {// this defines a hook function. This is defined, not executed. Return createBean(beanName, MBD, args) is executed where the bean is actually created; } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; }});Copy the code

The actual timing of the call is: inside the getSingleton method. Callback interface functions.

public Object getSingleton(String beanName, ObjectFactory<? > singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); Synchronized (this.singletonObjects) {// synchronized (this.singletonObjects) { From a level 1 cache singleton 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!) "); } if (logger.isDebugEnabled()) { logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } / / the second step: add bean to the singletonsCurrentlyInCreation, said bean is creating beforeSingletonCreation (beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try {// step 3: Here call getObject () hook method, can callback function, anonymous call singletonFactory createBean () singletonObject = singletonFactory. GetObject (); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. 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

Second call: this is when the level 3 cache is defined

Call addSingletonFactory (…). Defines a hook function. This is just a definition, not an execution

// Wrap our earlier object as a singletonFactory object, which provides getObject() to put the static bean in the tertiary cache. () -> getEarlyBeanReference(beanName, mbd, bean));Copy the code

And then I go inside addSingletonFactory, and I just put the singletonFactory into the level 3 cache, which is just defined and not executed

protected void addSingletonFactory(String beanName, ObjectFactory<? > singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (! This. SingletonObjects. Either containsKey (beanName)) {/ / added to the three-level cache, exposed early object is used to solve circular dependencies. Enclosing singletonFactories. Put (beanName, singletonFactory); / / deleted from the second level cache enclosing earlySingletonObjects. Remove (beanName); / / added to the already registered the singleton instance. This. RegisteredSingletons. Add (beanName); }}}Copy the code

When was it executed? When I fetch an object from the cache.

@Nullable protected Object getSingleton(String beanName, Boolean allowEarlyReference) {/ / get the bean instance from level 1 cache Object Object singletonObject = this. SingletonObjects. Get (beanName); /** * If no object is retrieved from the first level cache, And singletonsCurrentlyIncreation is true, that is, the class is created. * indicate the current is dependent on a cycle. * * have deal with the issue of circular dependencies. Here, we use the circular dependencies * / if l3 cache solution (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { /** * Get the bean from the secondary cache. The object in the secondary cache is an early object. * What is an early object? Is the bean just call the constructor, also did not give the property of the bean assignment, and initialization, it is early object * / singletonObject = this. EarlySingletonObjects. Get (beanName); If (singletonObject == null && allowEarlyReference) {/** ** singletonFactories are used to solve the problem of loop dependency. When the bean calls the constructor, wrap the early object as an ObjectFactory object, which is exposed in the tertiary cache */ ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) {/** * here the object is wrapped with an exposed ObjectFactory. By calling his getObject () method to get object * getEarlyBeanReference will call in this link () to the rear handle * / singletonObject = singletonFactory. GetObject (); / / this. In the early object placed in the second level cache earlySingletonObjects. Put (beanName singletonObject); / / delete the l3 cache this. SingletonFactories. Remove (beanName); } } } } return singletonObject; }Copy the code

Here called l3 cache, singletonObject = singletonFactory getObject (); The callback hook function.