This article has been included in the public number: https://mp.weixin.qq.com/s/FIm84EGVV21phajCaLjgaA

preface

You may have questions like:

1. I want to read Spring source code, but I don’t know how to start reading it. I have no idea about the whole Bean process, and I have no clue about related problems

2, read the source code several times, can’t completely understand, no feeling, did not forget for a while

In this article, the source code will be derived from practical problems, and the explanation will try to give you a step-by-step understanding of The IOC, DI, lifecycle, scope, and so on of Spring beans in the form of diagrams.

Let’s start with a circular dependency problem

The phenomenon of

Circular dependencies are circular references, where two or more beans hold each other and form a closed loop. For example, A depends on B, B depends on C, and C depends on A. The diagram below:



How to understand “dependency” in Spring:

  • Constructor loop dependencies
  • The field property injects loop dependencies

Directly on the code:

Constructor loop dependencies

@Service
public class A {  
    public A(B b) {  }
}
Copy the code

@Service
public class B {  
    public B(C c) {  
    }
}
Copy the code

@Service
public class C {  
    public C(A a) {  }
}Copy the code

Result: The project failed to start and a cycle was found.



2. The field property injects cyclic dependencies

@Service
public class A1 {  
    @Autowired  
    private B1 b1;
}Copy the code

@Service
public class B1 {  
    @Autowired  
    public C1 c1;
}Copy the code

@Service
public class C1 {  
    @Autowired  public A1 a1;
}Copy the code

Result: The project started successfully



3. Field property injection loop dependency (prototype)

@Service
@Scope("prototype")
public class A1 {  
    @Autowired  
    private B1 b1;
}Copy the code

@Service
@Scope("prototype")
public class B1 {  
    @Autowired  
    public C1 c1;
}Copy the code

@Service
@Scope("prototype")
public class C1 {  
    @Autowired  public A1 a1;
}Copy the code

Result: The project failed to start and a cycle was found.

Conclusion: Constructor injection and Property injection of type Prototype will fail to initialize beans in the same loop-dependent scenario. Since @service is singleton by default, singleton property injection can be successful.

The analysis reason

Analysis of the reason is in the process of discovering SpringIOC, if you are not interested in the source code, you can pay attention to the summary of each section of source code analysis and the analysis of cyclic dependency problems.

Loading process of SpringBean (source code analysis)

A simple piece of code as an entry point

ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
ac.getBean(XXX.class);Copy the code

ClassPathXmlApplicationContext is a class loading XML configuration files, and also the relative AnnotationConfigWebApplicationContext, these two classes of poor is not poor, Just ClassPathXmlApplicationContext Resource is an XML file and AnnotationConfigWebApplicationContext is Scan annotations.

You can get the instance of the bean directly from the second line, so when you construct the method in the first line, you’re done loading all the beans.

ClassPathXmlApplicationContext, for example, he stored contents are as follows:

Object name The class type As with Belong to the class
configResources Resource[] Array of configuration file resource objects ClassPathXmlApplicationContext
configLocations String[] Configuration file string array that stores the configuration file path AbstractRefreshableConfigApplicationContext
beanFactory DefaultListableBeanFactory Bean factories used by the context AbstractRefreshableApplicationContext
beanFactoryMonitor Object The synchronization monitor used by the Bean factory AbstractRefreshableApplicationContext
id String The unique Id used by the context to identify this ApplicationContext AbstractApplicationContext
parent ApplicationContext The parent ApplicationContext AbstractApplicationContext
beanFactoryPostProcessors List<BeanFactoryPostProcessor> Stores the BeanFactoryPostProcessor interface, an extension point provided by Spring AbstractApplicationContext
startupShutdownMonitor Object A monitor common to the refresh method and the DeStory method to avoid simultaneous execution of both methods AbstractApplicationContext
shutdownHook Thread Spring provides a hook that runs methods in Thread when the JVM stops executing AbstractApplicationContext
resourcePatternResolver ResourcePatternResolver The resource format parser used by the context AbstractApplicationContext
lifecycleProcessor LifecycleProcessor A lifecycle handler interface for managing the Bean life cycle AbstractApplicationContext
messageSource MessageSource An interface to implement internationalization AbstractApplicationContext
applicationEventMulticaster ApplicationEventMulticaster The event multicast interface in the event management mechanism provided by Spring AbstractApplicationContext
applicationListeners Set<ApplicationListener> Application listeners in the event management mechanism provided by Spring AbstractApplicationContext

The construction method is as follows:



Next, look roughly at the refresh method:



Instead of looking at the structure of the refresh method, there are a few things worth learning:

1. Why lock the method? This is to avoid refreshing the Spring context at the same time in multi-threaded scenarios

StartUpShutdownMonitor (Synchronized); startUpShutdownMonitor (Synchronized);

(1) The close() method is called when the resource is closed. The close() method also uses the same object lock, and the close() method is closed and refresh two conflicting methods, so as to avoid the conflict

(2) Compared with the whole method, the scope of synchronization is smaller, the granularity of lock is smaller and the efficiency is higher

3. This method refresh defines the entire Spring IOC process, and each method name is clear, easy to understand, maintainable, and readable

Conclusion: look at the source code need to find the correct entry, look at the time to think more, learn the clever design of Spring. The key constructor of the ApplicationContext is refresh, which has some good design.

ObtainFreshBeanFactory method

This method gets the Bean factory that refreshes the Spring context:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {  
    this.refreshBeanFactory();  
    return this.getBeanFactory();
}Copy the code
protected final void refreshBeanFactory() throws BeansException {  
    if (this.hasBeanFactory()) {    
        this.destroyBeans();    
        this.closeBeanFactory();  
    }  
    try {    
        DefaultListableBeanFactory beanFactory = this.createBeanFactory();    
        beanFactory.setSerializationId(this.getId());    
        this.customizeBeanFactory(beanFactory);    
        this.loadBeanDefinitions(beanFactory);    
        synchronized(this.beanFactoryMonitor) {      
            this.beanFactory = beanFactory;    }  
        } catch (IOException var5) {    
            throw new ApplicationContextException("I/O error parsing bean definition source for "+ this.getDisplayName(), var5); }}Copy the code

This is DefaultListableBeanFactory off the core of the code, the core class we’ll tidy it up, to chart formats:

Here are three bold maps that are the key to solving the problem… We’ll break it down later

Object name The class type As with Belong to the class
aliasMap Map<String, String> Store Bean names ->Bean alias mappings SimpleAliasRegistry
singletonObjects Map<String, Object> Store the singleton Bean name -> Singleton Bean to implement the mapping DefaultSingletonBeanRegistry
singletonFactories Map<String, ObjectFactory> Store the Bean name ->ObjectFactory to implement the mapping DefaultSingletonBeanRegistry
earlySingletonObjects Map<String, Object> Store the Bean name -> Preload the Bean to implement the mapping DefaultSingletonBeanRegistry
registeredSingletons Set<String> Store registered Bean names DefaultSingletonBeanRegistry
singletonsCurrentlyInCreation Set<String> Stores the name of the Bean currently being created DefaultSingletonBeanRegistry
disposableBeans Map<String, Object>

Store the Bean name ->Disposable interface implementation Bean implements the mapping

DefaultSingletonBeanRegistry
factoryBeanObjectCache Map<String, Object> Store Bean names ->FactoryBean Interface Beans implement mappings FactoryBeanRegistrySupport
propertyEditorRegistrars Set<PropertyEditorRegistrar> Store the PropertyEditorRegistrar interface implementation collection AbstractBeanFactory
embeddedValueResolvers List<StringValueResolver> Stores a list of StringValueResolver (string parser) interface implementations AbstractBeanFactory
beanPostProcessors List<BeanPostProcessor> Stores a list of BeanPostProcessor interface implementations AbstractBeanFactory
mergedBeanDefinitions Map<String, RootBeanDefinition> Store the Bean name -> The merged root Bean defines the mapping AbstractBeanFactory
alreadyCreated Set<String> Stores a collection of Bean names that have been created at least once AbstractBeanFactory
ignoredDependencyInterfaces Set<Class> Stores a collection of interface Class objects that are not auto-assembled AbstractAutowireCapableBeanFactory
resolvableDependencies Map<Class, Object> Store modified dependency mappings DefaultListableBeanFactory
beanDefinitionMap Map<String, BeanDefinition> Store the Bean name –>Bean defines the mapping DefaultListableBeanFactory
beanDefinitionNames List<String> Store a list of Bean definition names DefaultListableBeanFactory  

BeanDefinition is registered in the IOC container

LoadBeanDefinitions is briefly analyzed.

Here’s how I understand this BeanDefinition: It is a product of the SpringIOC process and can be regarded as an abstraction of the Bean definition. The data encapsulated init are all related to the Bean definition and encapsulate some basic Bean properties, Initi-method, destroy-method, etc.

The main method here is loadBeanDefinitions, which I won’t go into detail on here, but it does several things:

BeanDefinitionReader initialized

Take the BeanDefinitionReader for Resource, which is the location of the XML configuration file, and convert the file to an object called Document

3. Next, we need to convert the Document object into the container’s internal data structure (BeanDefinition), that is, to parse the Bean’s List, Map, Set and other elements. Converted to a Managed class (Spring’s wrapper for BeanDefinition data) and placed in BeanDefinition; This method is RegisterBeanDefinition(), which is the process of parsing.

4. After parsing is complete, the parsed results are put into a BeanDefinition object and set into a Map

This process is the registration of BeanDefinition in the IOC container.

Back to the Refresh method, summarize each step as shown below:


Summary: This part of the process is about how Spring loads an Xml file or annotation and parses it into a BeanDefinition.

The process by which Spring creates beans

Going back to the refresh method (that is, when constructing the ApplicationContext), let’s skip the unimportant bits:



We see finishBeanFactoryInitialization inside preInstantiateSingletons method directly, as the name suggests the initialize all singleton beans, intercept part is as follows:



Now look at the core getBean method, which is used for all instances that getBean objects, and which ultimately calls the doGetBean method, which is where so-called DI (dependency injection) occurs.

Dependency injection is done when BeanDefinition is ready. This process is not easy because Spring provides many parameter configurations, each of which represents the characteristics of the IOC container. The implementation of these features needs to be done during the Bean’s life cycle.

An AbstractBeanFactory doGetBean method is an example of a dependency injection process:



Conclusion: Once Spring has created the BeanDefinition, it starts instantiating the Bean and populating the Bean’s dependent properties. CGLIB or Java reflection technology is used underneath the instantiation. The instantiateBean core PupulateBean method in the image above is important!

Analysis of cyclic dependency problems

Let’s summarize our previous conclusions:

Constructor injection and Prototype field injection cannot be initialized with circular dependencies

2. When field injects a singleton bean, the bean can still be successfully initialized despite the loop dependency

In view of these conclusions, some questions are raised

  1. How do singleton set-value injection beans solve the problem of loop dependency? If B is injected into A, what is the order in which they are initialized?
  2. Why can’t Spring of prototype type and constructor type solve loop dependencies?

The DefaultListableBeanFactory before class, lists a table; Now LET me list the key essential attributes:

Level 1 cache: Private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64); Level 2 cache: /** Save all Bean objects created earlier, */ Private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16); Private final Map<String, ObjectFactory<? >> singletonFactories = new HashMap<String, ObjectFactory<? > > (16); Private final Set<String> registeredSingletons = new LinkedHashSet<String>(64); / * * id to specify the name of the Bean object is in the create state This state is very important * / private final Set < String > singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));Copy the code

To understand this problem, we only need to focus on the “third level”, or singletonFactories, of the first three maps, which we call singleton initialization

Analysis:

For problem 1, the singleton set injection, if B is instantiated from A, B should be an attribute in A, and the guess is that B should be initialized when populateBean is instantiated from A after A has been instantiated.

For q2, instantiate is simply A process of instantiating an object, and the constructor must be instantiated while new, so it is assumed that A should instantiate B.

Now that I have my analysis and guess in hand, AROUND the key attributes, I have compiled the following code from the doGetBean method above to populateBean:



The first method getSingleton gets the Singleton from the singletonFactories, and the second method getSingleton gets the Singleton from the singletonFactories. And the addSingletonFactory will put the Singleton into the singletonFactories.

For question 1: how does a singleton’s set-value injection bean solve the problem of loop dependency? If B is injected into A, what is the order in which they are initialized?

Suppose cyclic injection is a-B-A: A depends on B(A autowire B), and B depends on A (B autowire A) :



The essence is that level 3 caching comes into play and solves the loop.

For nQ2, instantiate means new object, and the constructor should be executed on new object, so it is supposed that A should instantiate B.

The answer is simple, because the constructor in A injected B, then A initialized B before the key method addSingletonFactory(), resulting in no A in the level 3 cache at all, so an infinite loop occurred, and Spring found it and threw an exception. As for how Spring finds exceptions, it essentially marks the Bean based on its state. If the Bean is being created when the recursive call is made, it simply throws a loop-dependent exception.

So how is Prototype’s Bean initialized?

Prototypebeans have one key property:

/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
	new NamedThreadLocal<Object>("Prototype beans currently in creation");
Copy the code

The beanName that holds the prototype being created does not expose any factory caches on the process. And puts the beanName of each prototype being created in a set when using the beforePrototypeCreation(String beanName) method:

protected void beforePrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal == null) {
			this.prototypesCurrentlyInCreation.set(beanName);
		}
		else if (curVal instanceof String) {
			Set<String> beanNameSet = new HashSet<String>(2);
			beanNameSet.add((String) curVal);
			beanNameSet.add(beanName);
			this.prototypesCurrentlyInCreation.set(beanNameSet);
		}
		else{ Set<String> beanNameSet = (Set<String>) curVal; beanNameSet.add(beanName); }}Copy the code

If beanName is in the created state, it will throw an exception:

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return(curVal ! = null && (curVal.equals(beanName) || (curVal instanceof Set && ((Set<? >) curVal).contains(beanName)))); }Copy the code

As you can see from the flow, the second time a getBean method enters the same Bean, whether it is a construct injection or a set value injection, it must throw an exception in the validation part, so the injection cannot be completed and the circular reference cannot be implemented.

Summary: Spring executes the constructor method on InstantiateBean, constructs the instance, or if it’s a singleton, puts it into onesingletonBeanFactoryThe populateBean method is used to set the properties. Loop dependencies are addressed through a cache of singletonBeanFactory.

Let’s do one more problem

Now that you have a feel for the whole Spring process, let’s address a simple common problem:

Consider the following Singleton code:

    @Service
    public class SingletonBean{

       @Autowired 
       private PrototypeBean prototypeBean;

       public void doSomething(){ System.out.println(prototypeBean.toString()); }}Copy the code

     @Component 
     @Scope(value="prototype")
     public class PrototypeBean{
     }Copy the code

A Singleton Autowired a prototype in the Bean Bean, then the problem comes, each invocation SingletonBean. DoSomething () when the object is the same print?

With the previous knowledge reserve, let’s briefly analyze: Because a Singleton is a Singleton, it is initialized at project startup, and the prototypeBean is essentially just one Property of it, Then there is only one SingletonBean in ApplicationContex and a PrototypeBean of type prototype created when SingletonBean is initialized.

So each call SingletonBean. DoSomething (), the Spring will obtain from ApplicationContex SingletonBean, every time get SingletonBean is the same, So even though protoTypeBeans are prototype, protoTypeBeans are still the same. It must print the same memory address every time.

So how can this problem be solved?

The solution is simple. In this case, we cannot inject a prototypeBean. We have to manually call getBean(“prototypeBean”) at runtime.

@Service public class SpringBeanUtils implements ApplicationContextAware { private static ApplicationContext appContext;  @Override public voidsetApplicationContext(ApplicationContext applicationContext) throws BeansException { 
       SpringBeanUtils.appContext=applicationContext;  
    }  
    public static ApplicationContext getAppContext() {    
        return appContext;  
    }  
    public static Object getBean(String beanName) {    
        checkApplicationContext();    
        return appContext.getBean(beanName);  
    }  
    private static void checkApplicationContext() {    
        if (null == appContext) {      
            throw new IllegalStateException("ApplicaitonContext not injected");   
         }  
    }  
    @SuppressWarnings("unchecked") public static <T> T getBean(Class<T> clazz) { checkApplicationContext(); Map<? ,? > map = appContext.getBeansOfType(clazz);returnmap.isEmpty() ? null : (T) map.values().iterator().next(); }}Copy the code

For this ApplicationContextAware interface:

In special cases where a Bean needs to implement a function that must be implemented with the help of the Spring container, the Bean must first obtain the Spring container and then implement the function with the help of the Spring container. To have a Bean get its Spring container, you can have the Bean implement the ApplicationContextAware interface.

Interested readers can try it out for themselves.

Conclusion:

Going back to the issue of loop dependencies, one might ask that singletonBeanFactory is only a tier 3 cache, so what’s the use of tier 1 and tier 2 caches?

Spring can initialize A Singleton in several steps: initialization — set — destruction. In A loop dependent scenario, there is only A — B — A sequence, but in A concurrent scenario, the getBean method may be called at each step. A singleton Bean needs to have only one instance, so Spring uses these caches plus object locks to solve this problem while eliminating unnecessary duplication. Spring lock granularity selection is also very hanging, here will not be studied in depth.

The key to solve such problems is to have a good idea of the whole process of SpringIOC and DI. Looking at the source code in general, it is not required that each line of code be thoroughly understood, but it is necessary to know the whole process and what to do in each process, so that the actual problems can be quickly analyzed and solved.

Hopefully this article has helped you gain a deeper understanding of Spring’s IOC and DI processes!