2021.5.17: Blog Rewrite plan iii, 1. Add introductions such as simple factories and some design patterns 2. According to the original logic, add to solve the problem of cycle dependence 3. Add the implementation method of Bean life cycle division and the method of Bean dynamic extension according to the life cycle 4. Optimize the original narrative logic and description of ambiguous areas

How does the Spring container create beans and manage their life cycle?

The Spring container’s responsibilities:

1. Business object construction management: that is, business objects do not need to care about how to build and obtain the dependent objects, but this part of work is transferred to the Spring container. The container needs to separate the construction logic of the dependent objects from the business objects, so as not to pollute the implementation of business objects.

2. Dependency binding between business objects: The Spring container injects the dependent objects into the binding by combining all business objects previously built and managed, as well as identifiable dependencies between each object, to ensure that each business object is in a ready state when it is used.

How the Spring container does this:

Responsibility 1- Build management of business objects

For container responsibility 1, it is obvious that we can use a simple factory or factory pattern to separate the use of business objects from the object logic needed to build them;

The following code is the process of the PizzaController class from creating Pizza by itself to using a simple factory to rely on PizzaSimpleFactory to create a specific Pizza type (Pizza is an abstract interface, and the specific Pizza implementation includes MetaPizza, RedPizza, etc.) :

public class PizzaController { public static void orderPizza(int type) throws Exception{ // 1. Pizza Pizza = getPizzaByType(type); // 2. Prepare (); pizza.cut(); pizza.take(); } // This method is subject to change, such as adding or removing a type of Pizza // according to the design principle that classes should be open for extension and closed for modification, Private static Pizza getPizzaByType(int type) throws Exception{if (type == PizzaEnum.meta. GetType ()) { return new MetaPizza(); } if (type == PizzaEnum.CHEESE.getType()) { return new CheesePizza(); } if (type == PizzaEnum.RED.getType()) { return new RedPizza(); } throw new Exception("current type is not support"); } public static void main(String[] args) throws Exception{ orderPizza(2); }}Copy the code

The above code is the code that needs to be changed. The object is still manually new the dependent object. We first use a simple factory to decouple the dependencies between objects.

/* Simple factory implementation, */ public class PizzaSimpleFactory {public pizzabyType (int Type) throws getPizzaByType(int type) Exception{ if (type == PizzaEnum.META.getType()) { return new MetaPizza(); } if (type == PizzaEnum.CHEESE.getType()) { return new CheesePizza(); } if (type == PizzaEnum.RED.getType()) { return new RedPizza(); } throw new Exception("current type is not support"); } } public class PizzaController { PizzaSimpleFactory pizzaSimpleFactory; PizzaSimpleFactory public PizzaController(PizzaSimpleFactory PizzaSimpleFactory) { this.pizzaSimpleFactory = pizzaSimpleFactory } public static void orderPizza(int type) throws Exception{ // 1. To obtain the corresponding depending on the type of the incoming Pizza Pizza instance (PizzaController relied on to create PizzaSimpleFactory) Pizza Pizza. = PizzaSimpleFactory getPizzaByType (type);  // 2. Prepare (); pizza.cut(); pizza.take(); } public static void main(String[] args) throws Exception{ orderPizza(2); }}Copy the code

You can see that by using a simple factory, we decouple object instantiation from object usage, eliminating the need for upper-layer components to manually new() objects.

However, the above code still has a certain resource waste problem – every time we get the corresponding instance through PizzaSimpleFactory, we re-new the object, so we can improve resource utilization by storing the reference of the object through the data structure:

Public class PizzaSimpleFactory {static map <Integer, Pizza> pizzaMap = new HashMap<>(4); static { pizzaMap.put(PizzaEnum.META.getType(), new MetaPizza()); pizzaMap.put(PizzaEnum.CHEESE.getType(), new CheesePizza()); pizzaMap.put(PizzaEnum.RED.getType(), new RedPizza()); } public Pizza getPizzaByType(int type) throws Exception{ return pizzaMap.get(type); }}Copy the code

Spring also implements the container in a simple factory manner. In Spring, we can also implement the following code:

/ / ClassPathXmlApplicationContext current can be as simple as simple to store all the Bean factory / / we can do we want to get to the name of the class to get to the corresponding interface implementation class reference, formally described above with us. public void getBean() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml"); UserService userService = beanFactory.getBean("userService", UserService.class); }Copy the code

In the Spring container, the BeanFactory acts as a simple factory, exposing the method to get the corresponding object via getBean(). (The general direction is analyzed here, and the details will be described later)

Responsibility 2- Dependent binding between business objects

Most real-world classes are not like Pizza subclasses, which can be initialized directly with new();

For example, Userdao.class may depend on other classes such as Usermapper.class, so when initializing UserDao.class, the container needs to get a reference to the UserMapper.class object through getBean() based on the dependency. (PS: with the introduction of BeanFactory, there is no manual new dependency object in the user object, instead, it is getBean according to the dependency.)

Therefore, we must provide a mechanism for the container to read dependencies and solve two problems: where the container should get the dependency information between objects; How the container parses the dependency information completes the initialization of the object.

How do you declare object dependencies

The most common way to store object dependencies is through XML files or annotations that describe object dependencies such as @AutoWired and @Resource. You can also describe object dependencies through Java configuration classes.

An example of an XML configuration is as follows:

<beans> <bean id = "pizzaController" class=".. PizzaController" lazy-init="true"> <constructor-args ref="pizzaSimpleFactory"> </bean> <bean ID = "pizzaSimpleFactory" class=".. PizzaSimpleFactory"/ scope="prototype"> </beans>Copy the code

The PizzaController class depends on the PizzaSimpleFactory classes and needs to pass in a constructor reference to the dependent object.

A note is more direct:

Public class PizzaController {// Declare by annotation that the PizzaController class depends on the PizzaSimpleFactory class @autoWired Private PizzaSimpleFactory pizzaSimpleFactory; }Copy the code

How do I read and manage dependencies

When a bean is defined as a file, code, or annotation, the dependency information is stored in the appropriate location, so the container needs to provide a reading mechanism for each object that the container needs to manage, and store it in a data object that uniquely corresponds to that object (BeanDefinition).

This reading mechanism is defined by the BeanDefinitionReader interface. Spring provides different implementations for different bean definitions. The corresponding XML class is XmlBeanDefinitionReader.

After the container reads the dependency information through BeanDefinitionReader, the BeanDefinitionRegistry() interface is abstracted to manage the dependency relationships.

With these two steps in mind, let’s take reading XML as an example, using unit test classes from Spring source code

The class ComponentBeanDefinitionParserTests {/ / DefaultListableBeanFactory class for BeanDefinitionRegistry an implementation class private final DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @beforeall void setUp() throws Exception {// Call XmlBeanDefinitionReader to load bean definitions in the 'component-config.xml' file Save this definition information to beanFactory (bf). New XmlBeanDefinitionReader(bf). LoadBeanDefinitions (new ClassPathResource( "component-config.xml", ComponentBeanDefinitionParserTests.class)); }}Copy the code

The above steps are the same as the flow we analyzed above. We will not explain how readers read the XML information, because the focus of this article is to take advantage of the extensibility the Spring framework provides to consumers by analyzing the Spring loading process.

Then look at the definition of BeanDefition, which is essentially the Bean information that is stored in XML or annotations:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { String SCOPE_SINGLETON = "singleton"; String SCOPE_PROTOTYPE = "prototype"; // omit some code... void setParentName(@Nullable String var1); Void setBeanClassName(@nullable String var1); void setBeanClassName(@nullable String var1); // The class attribute in XML provides information for reflection newInstance() void setScope(@nullable String var1); // The scope attribute defined in XML is singleton by default. // The scope attribute defined in XML is singleton. Instead, create void setLazyInit(Boolean var1) when the object needs to be injected; / / attention! Void setDependsOn(@nullable String...) void setSon (@nullable String...) var1); @Nullable String[] getDependsOn(); Void setInitMethodName(@nullable String var1); void setInitMethodName(@nullable String var1); // XML custom void setDestroyMethodName(@nullable String var1); }Copy the code

Now we know how the Spring container loads and stores BeanDefinition;

useBeanDefitionLoad the class

Above, we briefly analyzed the implementation direction of the Spring container through the two responsibilities, and the state of the Spring container is also divided into two stages: the initial BeanDefition stage and the instance Bean stage.

In the Spring container, we can register the corresponding Bean in the container by XML file or annotation +JavaConfig. This paper analyzes the common implementation of the latter in Spring IOC.

And in the process of analysis and implementation interspersed to solve the following problems:

  1. Spring BeanLife cycle andBeanFactoryPostProcessor,BeanPostProcessorThe relationship between these processors;
  2. SpringWhen and how the default post-processor is registered in the container;
  3. SpringHow is cache dependency resolved?
  4. Now that we understand this mechanism, what can we do?

Initialize theBeanDefitionphase

Getting a BeanDefinition through annotations is a bit more convoluted than getting a BeanDefinition through a direct XML file.

First of all, we need will be @ the Configuration modified Configuration class in AnnotationConfigApplicationContext, then will the Spring container Configuration class BeanDefinition through the register () method to register into the container, It also registers the BeanDefinition of the backend processor in Spring with the container.

ConfigurationClassPostProcessor(implements BeanDefinitionRegistryPostProcessor), AutowiredAnnotationBeanPostProcessor (implements SmartInstantiationAwareBeanPostProcessor), CommonAnnotationBeanPostProcessor (implements InstantiationAwareBeanPostProcessor)…

BeanFactoryPostProcessor and BeanPostProcessor are two interfaces in essence.

Note that BeanFactoryPostProcessor is distinguished from BeanPostProcessor, which is the container initialization layer, and BeanPostProcessor, which is the Bean initialization layer.

Such as spring BeanFactoryPostProcessor stretched out (extends) two interfaces – BeanDefinitionRegistryPostProcessor, spring BeanFactoryPostProcessor, The instanceof feature gives a higher priority to the subinterface to split the Spring initialization BeanDefinition into two phases.

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
Copy the code
Public static void invokeBeanFactoryPostProcessors () {/ / all of the traversal container ` spring BeanFactoryPostProcessor ` interface implementation class:  for (BeanFactoryPostProcessor postProcessor : BeanFactoryPostProcessors) {/ / by instanceof that only BeanDefinitionRegistryPostProcessor interface implementation class priority if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor; / / call interface methods registryProcessor. PostProcessBeanDefinitionRegistry (registry); registryProcessors.add(registryProcessor); } else { regularPostProcessors.add(postProcessor); }} // omit some code // Then get all the BeanFactoryPostProcessor interface implementation classes and call the interface method String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { ... }}Copy the code

It can be seen that Spring defines the execution sequence of interfaces through the parent-child interface relationship, thus realizing the staging of container flow.

Similarly, by extending BeanPostProcessor’s subinterface, Such as InstantiationAwareBeanPostProcessor (container Bean instantiation phase), BeanPostProcessor (involved in container Bean initialization phase) and so on interface so as to realize the phase of the process of container.

throughBeanFactoryPostProcessorThe dynamic changeBeanDefition:

In the initial stage of BeanDefition, we can dynamically modify BeanDefition by implementing the BeanFactoryPostProcessor interface

@FunctionalInterface
public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
        throws BeansException;
}
Copy the code

In everyday programming, a common scenario is when we configure properties such as database-related beans:

<! <bean id="dataSourceConfig" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:config/jdbc.properties</value> <value>file:${config.home}/jdbc.properties</value> </list> </property> <property name="ignoreResourceNotFound" value="true"/> </bean>Copy the code

There are always dynamic configuration information like ${XXX} that needs to be obtained from jdbc.properties, but it is not appropriate for us to read the string with the defitionReader and store it in the BeanDefitionReader. So we need a specific BeanFactoryPostProcessor interface implementation class that reads jdbC. properties and iterates through the ${} placeholders for each BeanDefition.

The code is as follows:

Through the abstract class PropertyResourceConfigurer postProcessBeanFactory spring BeanFactoryPostProcessor interface methods, inside the method through template pattern defines the processing properties of algorithm process:

public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered {// @param beanFactory, That need to be modified BeanDefinition instances of the current container @ Override public void postProcessBeanFactory (ConfigurableListableBeanFactory the beanFactory) throws BeansException { try { Properties mergedProps = mergeProperties(); // Convert the merged properties, if necessary. convertProperties(mergedProps); // Let the subclass process the properties. // Let the subclass process the properties. ProcessProperties (beanFactory, mergedProps); } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); }}}Copy the code

By dynamically modifying the Beandefitions on the container level, we can achieve the dynamic replacement of Bean attributes, which improves the expansibility of the code and reduces the coupling degree.

Bean Construction phase

Both FactoryBean and ApplicationContext fetch objects by calling getBean(), and getBean() calls createBean() on the first fetch; The difference is that the former waits for the object to be created before getting the object, while the latter automatically gets the object when the container is initialized.

The following describes the entire Bean instantiation phase as it progresses through its life cycle:

Only non-Web beans are discussed here, i.e. there are only two scopes: singleton, Prototype:

Singleton: A container creates only one object (unlike the singleton pattern, which guarantees only one instance object to be loaded under the same classloader) and holds a reference to that object for its lifetime, usually unregistered only when the container is destroyed;

Prototype: the container can create multiple objects, and the container is created after the object is no longer responsible for, but by the specific object of the caller to hold references and maintain life cycle (it is worth noting that each caller after get an object reference has been a reference before reuse, rather than every time new generated a);

Instantiation phase

By the Spring container, in turn, calls in the instantiation process of Bean InstantiationAwareBeanPostProcessor interface method of four postProcessBeforeInstantiation (), postProcessAfterIns Tantiation (), postProcessProperties ()/postProcessPropertyValues (), The Bean instantiation is divided into four stages: before the instantiation, the corresponding constructor is selected through the policy mode to instantiate the object through reflection, and after the instantiation, the dependency object injection.

We will take a look at how Spring implements @AutoWired Field injection to get a feel for the Bean instantiation process, and how Spring solves the problem of property loop dependency.

PS: Methods of creating dependencies between objects include constructor injection, property injection, andSpringOnly property injection can be solved.

First we construct two Service classes that depend on each other’s properties and configure the packet scan path through the configuration class:

@Service public class OrderService { @Autowired private UserService userService; } @Service public class UserService { @Autowired private OrderService orderService; } @Configuration @ComponentScan("study.service") public class AppConfig { } public class MainApplication { public static Void main (String [] args) {/ / using AnnotationConfigApplicationContext, And into the configuration class AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (AppConfig. Class); }}Copy the code

As you can see, OrderService and UserService form an ABA cycle dependency problem. Let’s look at the solution:

In AnnotationConfigApplicationContext container starts, some BeanPostProcessor container automatically registered for internal use, including AutowiredAnnotationBeanPostProcessor, The post processor is mainly used for processing the @autowired annotation, when we are already complete instantiation object, and has been postProcessAfterInstantiation () method after processing, the final phase of the object to instantiate – attributes injection, here for Field Inject;

To distinguish objects in different time periods, the Spring container stores objects in different time periods using different maps:

/** 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);Copy the code

This is often referred to as a level 3 Cache on the web because all three map comments start with Cache of, But the essence of it is to distinguish between objects of different ages — singletonFactories, earlySingletonObjects, singletonObjects, We can use the getSingleton() method to get objects in order of their age priority:

@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 1. First try to obtain the initialization has completed Object Object singletonObject = this. SingletonObjects. Get (beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 2. Didn't get there, then try to access has attributes into complete object singletonObject = this. EarlySingletonObjects. Get (beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) {/ / 3. Not to get, then, try to obtain has constructed object singletonObject = singletonFactory. GetObject (); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }Copy the code

So when the Spring container first constructs an OrderService instance through the constructor, it puts the current instance into the singletonFactories Map, and then enters the property injection phase, The Spring container will invoke the AutowiredAnnotationBeanPostProcessor postProcessProperties parse () method dependence, found in the course of running the Field about the UserService existence of attribute dependence, Therefore, the method getBean() attempts to obtain the dependent object. GetBean () first calls getSingleton(), and there is no reference to the UserService object in any of the three maps. So the UserService object is generated by calling the getBean() method;

UserService generated by the same token, enter the attribute injection stage, the Spring container will pass the AutowiredAnnotationBeanPostProcessor postProcessProperties parse () method of dependence, GetBean () -> getSingleton(), because the singletonFactories Map already contains an instantiated Reference to OrderService. Thus, it returns directly and allows the build of UserService to continue without being blocked by cyclic dependencies.

Initialization phase (modifying beans)

Initialization phase through the BeanPostProcessor interface postProcessBeforeInitialization (), postProcessAfterInitialization () method of the Bean initialization phase into initialization, initialization, early before Start the last three stages.

Public interface BeanPostProcessor {// BeanPostProcessor preprocessor, Initialization before the call @ Nullable default Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { return bean; } // BeanPostProcessor, After initialization call @ Nullable default Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { return bean; }}Copy the code

For example, in the pre-initialization phase: Container, in turn, call all BeanPostProcessor interface implementation class postProcessBeforeInitialization () method, Such as ApplicationContextAwareProcessor implements BeanPostProcessor class, when the container calls the implementation class postProcessBeforeInitialization () method, It checks whether the Bean implements the Aware interface. If so, it will inject the object corresponding to the Aware interface. For example, if the Bean implements the ApplicationContextAware interface, the reference of the current container instance will be injected into the Bean.

Some of the more common ways to intervene in the Bean decoration phase in our daily programming are:

@ PostConstruct: container instantiation Bean called when registered into the container, and all the object implementing the BeanPostProcessor interface postProcessBeforeInitialization as object instantiation of pre-processing method; When instantiating a Bean that exists modified by this annotation, the pre-processing method in CommonAnnotation-BeanPostProcessor is called, which is our custom @Postconstrct modified method in our code.

InitializingBean:

public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}
Copy the code

Our own business class implements the Interface methods of InitializingBean, so after the container has set all the properties of the Bean, if the Bean implements the InitializingBean interface, its afterPropertiesSet method will be called;

So the afterPropertiesSet method is invoked after postProcessBeforeInitialization, which is the embodiment of the Spring Bean instantiation process.

PS: in particular, in all the lazy loading of Bean after completion of loading, will call all achieve SmartInitializingSingleton interface Bean afterSingletonsInstantiated () method;

We use the following code to test the order:

@Component
public class InitMethodsList implements InitializingBean, SmartInitializingSingleton {

    public void afterPropertiesSet() throws Exception {
        System.out.println("initializingBean");
    }

    public void afterSingletonsInstantiated() {
        System.out.println("smartInitializingSingleton");
    }

    @PostConstruct
    public void postConstructMethod() {
        System.out.println("CommonAnnotationBeanPostProcessor");
    }
}
Copy the code

The results are as follows:

CommonAnnotationBeanPostProcessor - Before the Init afterPropertiesSet Before initialization, initialization, After the Init afterSingletonInstantiated - After completion of all of the Bean After initializationCopy the code

It can be seen that the result is the same as the above initialization process. In daily development, when using these methods to participate in the initialization of container beans, we need to pay attention to the order of execution, and can also use the order of initialization of dependent related data.

Conclusion:

This article describes the two main responsibilities of the Spring container and briefly explains how they are implemented; Through the introduction of the two phases of container startup (initialization of BeanDefinition) and Bean initialization (instantiation of beans with Beantion), we introduce how to make use of the customizable space provided by the Spring IOC framework.