preface

In interviews, people are often asked about Spring’s IOC and DI (dependency injection). Many people think IOC is DI, but strictly speaking, the two are not the same, because IOC focuses on saving while dependency injection focuses on taking. In fact, we have another approach to dependency injection, and that is dependency lookup. We can think of both as implementations of IOC.

Dependency injection entry method

In the last article we talked about the IOC initialization process, but if you think back, didn’t it feel like something was missing? The IOC initialization simply stores the Bean definition file, but it doesn’t seem to have been initialized, and if a class refers to another class, it still needs to be assigned, which we haven’t covered, and which falls under dependency injection.

By default, dependency injection is triggered only when getBean() is called, because Spring is lazy by default. Lazy init=false or @lazy (value =false) is used to trigger dependency injection.

Dependency injection process analysis

Before analyzing the process, let’s look at the following example:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
applicationContext.getBean("myBean");
applicationContext.getBean(MyBean.class);
Copy the code

Our analysis starts with the getBean() method.

AbstractBeanFactory#getBean

Earlier we saw that the BeanFactory interface is a top-level interface that defines methods for manipulating beans. The ApplicationContext implements the BeanFactory interface indirectly. So its call to getBean() goes to a method in the AbstractBeanFactory class:

As you can see, you see the doXXX method directly after the call.

AbstractBeanFactory#doGetBean

After entering the doGetBean method, there are a series of judgments, mainly in the following aspects:

  1. Returns whether the current class is a singleton, and if so and the singleton has been created.
  2. The current prototypebeanIf it is being created, then a circular dependency is considered to have been created and an exception is thrown.
  3. Manually by@DependsOnComments orxmlWhether a dependency specified explicitly in the configuration has a cyclic dependency problem. If so, an exception is thrown.
  4. The currentBeanFactoryIn thebeanDefinitionMapWhether the current exists in the containerbeanThe correspondingBeanDefinitionIf it does not exist, it will go to the parent class to continue to get, and then call the corresponding parent classgetBean()Methods.

After a series of judgments, it determines whether the current Bean is a stereotype or a singleton, and then goes through different processing logic, but whether it’s a stereotype or a singleton, Eventually it will be called AbstractAutowireCapableBeanFactory createBean method to create the bean instance in the class

AbstractAutowireCapableBeanFactory#createBean

This method first verifies whether the current bean can be instantiated, and then has two main logic:

  1. Whether to return a proxy object, if yes.
  2. Just create onebeanObject instance.

Instead of focusing on the first logic, we’ll focus on the second logic, how to create a bean instance:

AbstractAutowireCapableBeanFactory#doCreateBean

This is another method that starts with do, indicating that this method actually creates a bean instance object. Before we analyze this method, we should first imagine what we would need to do in this method.

At the heart of this approach is to do two things:

  1. Instantiate abeanObject.
  2. Iterate over the properties of the current object, injecting others if necessarybeanIf it is found that an injection is requiredbeanIf it is not instantiated, you need to instantiate it first.

Create a bean instance (AbstractAutowireCapableBeanFactory# createBeanInstance)

In the doCreateBean method, the createBeanInstance method is called to instantiate a bean. There is a lot of logic involved in this, such as determining whether the class has public permissions, etc., but ultimately a bean instance is initialized by calling the current bean’s no-argument constructor or its argument constructor via reflection and then returning it wrapped as a BeanWrapper object.

But if this call is a constructor, and the parameter is a bean, so also will trigger first to bean in the initialization parameter, in addition to participation form of the constructor initializes the bean instance, relatively easier to understand, we have but to analyze details, mainly focuses on analysis of dependency injection treatment.

Dependency injection (AbstractAutowireCapableBeanFactory# populateBean)

Above create a Bean instance made, our object is not complete, because is only just creates an instance of the instance of the injected attribute is not populated, so you still need to complete the action of dependency injection, so at the time of dependency injection, if it is found that need to inject the object has not yet been initialized, It is also necessary to trigger the initialization of the injected object, and the injection can be divided into name injection and type injection (in addition to constructor injection, etc.) :

We use @autowired and @Resource annotations in dependency injection. One of the differences between the two annotations is that one is injected by type and the other is injected by name. But neither annotation actually follows the logic of name-injection and type-injection, But by corresponding AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor two beans rear processor, The @Resource annotation will also inject beans by type if they can’t be found by name. We won’t go into the details here. After all, our goal today is to analyze the whole dependency injection process.

Once the dependent properties are resolved by name or by property above, they are encapsulated in object MutablePropertyValues (i.e. : The applyPropertyValues() method is finally called to do the real property injection:

After processing, the applyPropertyValues() method is finally called for the actual property injection.

How is the circular dependency problem solved

Dependency injection after the success, the DI water even if is over, but we don’t have mentioned that there is A problem that is the problem of circular dependencies, circular dependencies is when we have two classes A and B, which rely on B, and rely on A, B or multiple classes, as well as form A loop dependence that belongs to the circular dependencies, For example, the following configuration is a typical cyclic dependency configuration:

<bean id="classA" class="ClassA" p:beanB-ref="classB"/>
<bean id="classB" class="ClassB" p:beanA-ref="classA"/>
Copy the code

When we explained the Bean initialization earlier, we said that when we initialize A, if we find that it depends on B, then the initialization of B will be triggered, but B depends on A, so it cannot complete the initialization. At this time, how should we solve this problem?

Before we look at how this problem is solved in Spring, let’s think for a moment about how we would solve this problem if we were in development. The method is as simple as you can imagine, that is, when we initialize the Bean, we cache it before we inject properties. In this way, we cache a semi-finished Bean to be exposed for injection.

However, there are three conditions that can not solve the problem of circular dependencies:

  • Circular dependencies generated by constructor injection. Cyclic dependencies created through constructor injection fail at the first initialization step and therefore cannot be exposed in advance.
  • Non-singleton patternBeanBecause this is only true in singleton modeBeanCache.
  • Manually setallowCircularReferences=false, indicates that circular dependencies are not allowed.

And processing circular dependencies in the middle of the Spring is also the idea, but in the Spring to consider the design problem, not only using a cache, but used the three cache, which is frequently asked in the interview cycle depend on the relevant problem of three-level cache (here is my personal opinion is not agree with the three levels of cache this term, After all, the three caches are three different containers in the same class, and there is no hierarchy between them. This is different from the two levels of caches used in MyBatis, but since everyone calls them that, we just follow the trend.

A three-level cache in Spring that addresses loop dependencies

As shown in the figure below, the singleton beans are cached in Spring by the following three containers (Map collections) :

  • singletonObjects

This container is used to store the finished singleton beans, known as a level 1 cache.

  • earlySingletonObjects

This is used to store semi-finished singleton beans, that is, beans that have not been injected with attributes after initialization, known as the second level cache.

  • singletonFactories

It stores Bean factory objects that can be used to generate semi-finished beans, known as tertiary caching.

Why do you need level 3 caching to solve loop dependencies

Look at the above level 3 cache, I don’t know if you have any questions, because level 1 cache and level 2 cache are relatively easy to understand, a finished product a semi-finished product, this is nothing to say, so why do you need level 3 cache, this is for what consideration?

Before answering this question, I combed the flow charts of scenarios with and without cyclic dependencies for comparative analysis:

The process for creating Bean A without loop dependencies:

Create Bean A process with cyclic dependencies (A depends on B, B depends on A) :

There is actually one big difference between these two processes, which I have highlighted in the following injection process with cyclic dependencies. That is, without cyclic dependencies, a class will first complete the injection of properties, and then call the BeanPostProcessor processor to do some post-processing. This also makes sense and is consistent with the Bean’s life cycle, but once there are cyclic dependencies, the BeanPostProcessor has to be processed in advance, which breaks the Bean’s life cycle to some extent.

This is where Spring Aop comes in. When we use Spring Aop, instead of using native objects, we should use proxy objects. When were proxy objects created?

In fact, Spring Aop’s proxy object is also done through BeanPostProcessor. Here is an example object using Spring Aop that has all the BeanPostProcessors:

Here has a rear AnnotationAwareAspectJAutoProxyCreator processor, namely Spring Aop is implemented through the post processor.

With this in mind, let’s confirm another problem. In Spring, to solve the problem of loop dependency, singleton beans are put into the cache after the Bean is initialized but before the properties are injected. However, native objects cannot be put into the secondary cache directly. Because this would be problematic if Spring Aop was used, other classes might inject native objects directly instead of proxy objects.

So can we just create a proxy object and store it in a level 2 cache? The answer is yes, but creating the proxy object directly would require calling the BeanPostProcessor postprocessor, which would violate the Bean declaration cycle by calling the postprocessor before property injection.

Spring does not know whether the current Bean has loop dependencies until the singleton is exposed in advance, so in order to delay the BeanPostProcessor as much as possible, Spring uses a level 3 cache, storing an Objectactory object, and does not create, Instead, when a cyclic dependency occurs, the object is created from the tertiary cache, because when a cyclic dependency occurs, the BeanPostProcessor has to be called in advance to initialize the instance.

Let’s look at the logic for adding level 3 caching:

The third level cache is added to store a lambda expression in order to delay creation. Finally, when a cyclic dependency occurs, the third level cache is retrieved when the Bean cannot be retrieved from the first or second level caches, which calls getObject() of the ObjectFactory. This method actually calls the getEarlyBeanReference below, where BeanPostProcessor is called ahead of time to create the instance.

conclusion

This paper mainly analyzes the main process of Spinrg dependency injection, and the problem of cyclic dependency generated in dependency injection is one of the more complex processing methods. In this paper, the detailed logic is omitted in the analysis process, only focusing on the main process. This article is mainly combined with some information on the Internet and then get their own debug debugging process of Spring dependency injection of a main process, if there is any misunderstanding, welcome to leave a message.