Tracking and parsing Spring ioc boot source code

Keywords: Java Spring Fragment

Zero Preparations

0 FBI WARNING

The article is extremely verbose and winding.

1 version

Spring version: spring 5.1.2.RELEASE

IDE : idea 2018.3

2 Bean Demo

package ioc;

/**
 * java bean
 */
public class Person {

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

3 Config Demo

package ioc;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Configuration class
 */
@Configuration
public class IocConfig {

    /**
        * Injecting a bean in code configuration
        */
    @Bean(name = "person")
    public Person getPerson(){
        Person person = new Person();
        person.setAge(100);
        person.setName("Zhang San");
        return person;
    }
}

4 main method

package ioc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class IocTest {

    public static void main(String[] args){
        ApplicationContext context = new AnnotationConfigApplicationContext(IocConfig.class);
        Person person = (Person) context.getBean("person");
        System.out.println(person.getName() + " , " + person.getAge());
    }
}

Initiation of a project and injection of beans

1 Overview

Spring's initialization is encapsulated in this line of code:

ApplicationContext context = new AnnotationConfigApplicationContext(IocConfig.class);

There are three lines of code inside this constructor method:

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
    this();                            // 2 parametric constructor
    register(annotatedClasses);        // 3 reader registration configuration class
    refresh();                        // 4 Create beans in bean factory
}

2 parametric constructor

The starting point of this part is that Annotation Config Application Context calls its own parametric constructor:

//AnnotationConfigApplicationContext.class
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
    this();                            // 2 parametric constructor
    register(annotatedClasses);
    refresh();
}

Tracking parametric constructors:

//AnnotationConfigApplicationContext.class
public AnnotationConfigApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this); //2.1
    this.scanner = new ClassPathBeanDefinitionScanner(this); //2.5
}

Annotated Bean Definition Reader registers Spring's own required beans and configuration beans into bean factory during its creation.

[ClassPathBean Definition Scanner is not used for the time being in this example. Without detailed analysis, just take a brief look at the construction method]

The parent Generic Application Context of Annotation Config Application Context creates a DefaultListableBeanFactory in the parametric constructor, which is the bean factory in this case:

//GenericApplicationContext.class
public GenericApplicationContext() {
    this.beanFactory = new DefaultListableBeanFactory();
}

Within DefaultListableBeanFactory, several Concurrent HashMap objects are maintained to store bean s in categories.

The most important map object is singletonObjects. This object is defined in DefaultSingleton bean Registry to store all singleton beans:

//DefaultSingletonBeanRegistry.class
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

2.1

In the process of creating Annotated Bean Definition Reader, several processor bean s required by spring are registered in DefaultListable Bean Factory. The main processes are as follows:

//AnnotatedBeanDefinitionReader.class
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
    //Call another parametric constructor of itself
    //The registry here is the Annotation Config Application Context itself
    //The getOrCreateEnvironment(registry) here eventually returns an object created by calling the Standard Environment parametric constructor.
    this(registry, getOrCreateEnvironment(registry));
}

//AnnotatedBeanDefinitionReader.class
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    //Parametric non-air validity
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    Assert.notNull(environment, "Environment must not be null");
    
    //Save Annotation Config Application Context
    this.registry = registry;
    //Create a comment parser, which you will use later
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);

    //Register the processor bean s that Spring needs
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

Environment, as its name implies, is Spring's environment, including the read of properties configuration, etc. This example is not used for the time being, press the table.

The core of the above code is to call the registerAnnotationConfigProcessors(...) method of AnnotationConfigUtils to continue tracking:

//AnnotationConfigUtils.class
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
    registerAnnotationConfigProcessors(registry, null);
}

//AnnotationConfigUtils.class
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
        BeanDefinitionRegistry registry, @Nullable Object source) {

    //Get the bean factory
    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    
    //The following code sets up two internal objects of the bean Factory and does not expand for the time being
    if (beanFactory != null) {
        if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
            beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
        }
        if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
            beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        }
    }

    //Create a new collection to return the wrapper class of all registered bean s at the end of the method, but in fact, in this case, no return value is received, so it can be ignored
    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

    //To determine whether a bean with this name exists in a bean factory, register a bean if it does not exist. The following are the same
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        //Package the registered processor class as a BeanDefinition
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        //In this case, source is null
        def.setSource(source);
        //Register bean s and add them to collections
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    //Both jsr250Present and jpaPresent are boolean variables
    //jsr250Present means checking whether the JSR-250 standard is supported, and jpaPresent means checking whether the JPA standard is supported.
    //Spring currently supports JSR-250 and JPA standards.
    if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition();
        try {
            def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
                    AnnotationConfigUtils.class.getClassLoader()));
        }catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
        }
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
    }

    if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
    }

    //Returns the above collection
    return beanDefs;
}

In this method, the main body code is the same, that is:

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    //Package the registered processor class as a BeanDefinition
    RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
    //In this case, source is null
    def.setSource(source);
    //Register bean s and add them to collections
    beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME : org.springframework.context.annotation.internalConfigurationAnnotationProcessor
CONFIGURATION_BEAN_NAME_GENERATOR : org.springframework.context.annotation.internalConfigurationBeanNameGenerator
AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME : org.springframework.context.annotation.internalAutowiredAnnotationProcessor
COMMON_ANNOTATION_PROCESSOR_BEAN_NAME : org.springframework.context.annotation.internalCommonAnnotationProcessor
PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME : org.springframework.context.annotation.internalPersistenceAnnotationProcessor
PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME : org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
EVENT_LISTENER_PROCESSOR_BEAN_NAME : org.springframework.context.event.internalEventListenerProcessor
EVENT_LISTENER_FACTORY_BEAN_NAME : org.springframework.context.event.internalEventListenerFactory

These constants are the paths and names of classes in spring.

registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)

This line of code calls the bean DefinitionMap object inside DefaultListableBeanFactory through Annotation Config Application Context, and finally queries whether the bean object has been registered. If it does not exist, the registration operation below is performed.

beanDefinitionMap is a Concurrent HashMap object that stores information about all registered bean s in DefaultListableBeanFactory, which is often mentioned later.

RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);

RootBeanDefinition is the implementation class of BeanDefinition. BeanDefinition is a descriptive wrapper interface for beans in Spring. It is used to store all kinds of information about beans, including and not limited to the bean's name, parent class, annotations, lazy loading and other attributes.

Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

BeanDefinition Holder is a support class for BeanDefinition, which stores BeanDefinition, bean name, and bean aliases (aliases).

[In Spring's bean registration process, there are many packaging and unpacking operations of Bean Definition and Bean Definition Holder, which need to be studied.)

registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)

The main function is the above line of code. Tracking the implementation of this method:

//AnnotationConfigUtils.class
private static BeanDefinitionHolder registerPostProcessor(
            BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {

    //Role Definition of Beans
    //BeanDefinition.ROLE_INFRASTRUCTURE = 2
    definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

    //Call the registerBeanDefinition method of AnnotationConfigApplicationContext
    registry.registerBeanDefinition(beanName, definition);

    //Return a wrapper class
    return new BeanDefinitionHolder(definition, beanName);
}

The role of BeanDefinition refers to the role definition of the bean in Spring. For Spring registered processor bean s, the definition values are both 2, meaning infrastructure.

In addition, role definitions include application and support.

There is no registerBeanDefinition(...) method inside the Annotation Config Application Context, but it inherits from Generic Application Context:

//GenericApplicationContext.class
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
    //2.2
    this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
}

This method actually calls the registerBeanDefinition(...) method of DefaultListableBeanFactory for bean registration.

2.2

Continue to track the internal implementation of the method:

//DefaultListableBeanFactory.class
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {

    //Non-null validation of parameters
    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    //Validation of bean s
    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }

    //beanDefinitionMap is a map object that stores all registered bean information
    //Normally, the null value should be retrieved here, i.e., the bean's information is not registered yet.
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);

    if (existingDefinition != null) {

        //If the bean already exists in the map, you need to determine whether it is allowed to override the bean definition
        if (!isAllowBeanDefinitionOverriding()) { //No, throw an exception
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        }else if (existingDefinition.getRole() < beanDefinition.getRole()) {
            if (logger.isInfoEnabled()) {
                logger.info("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        existingDefinition + "] with [" + beanDefinition + "]");
            }
        }else if (!beanDefinition.equals(existingDefinition)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }else {
            if (logger.isTraceEnabled()) {
                logger.trace("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        //As long as it is set to allow rewriting, the map will be updated at the end, except that the log s recorded will be different.
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }else { //Normally
        if (hasBeanCreationStarted()) { //2.3
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                    updatedSingletons.remove(beanName);
                    this.manualSingletonNames = updatedSingletons;
                }
            }
        }else {
            //Register beans in bean Definition Map
            this.beanDefinitionMap.put(beanName, beanDefinition);
            //Add bean name to bean Definition Names
            this.beanDefinitionNames.add(beanName);
            //Delete bean name from manualSingleton Names
            this.manualSingletonNames.remove(beanName);
        }
        //Empty array
        this.frozenBeanDefinitionNames = null;
    }

    //2.4
    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}

First, look at this line of code:

((AbstractBeanDefinition) beanDefinition).validate();

Track this line of code:

//AbstractBeanDefinition.class
public void validate() throws BeanDefinitionValidationException {
    if (hasMethodOverrides() && getFactoryMethodName() != null) {
        throw new BeanDefinitionValidationException(
                "Cannot combine static factory method with method overrides: " +
                "the static factory method must create the instance");
    }

    if (hasBeanClass()) {
        //This method is also mainly used for hasMethod Overrides (), which is ignored for the time being.
        prepareMethodOverrides();
    }
}

This method uses the hasMethod Overrides () and getFactoryMethodName() methods defined in AbstractBeanDefinition to determine whether the bean Definition is valid. If these two conditions are met at the same time, an exception will be thrown.

According to official documentation, hasMethodOverrides() returns true if the bean factory overrides the method in the bean; getFactoryMethodName() returns the name of the method if the factory method exists.

There is no special understanding of the application scenarios of these methods. According to the online data, there should be some relationship with the reflection creation of bean s.

Then look at this line of code:

this.frozenBeanDefinitionNames = null;

FrozenBean Definition Names is an array of strings defined in DefaultListable Bean Factory. Spring converts the bean Definition Names into an array after bean registration and assigns it to FrozenBean Definition Names.

[This is a memory optimization operation for Spring]

2.3

Look at the fragments of the above methods:

if (hasBeanCreationStarted()) {
    synchronized (this.beanDefinitionMap) { //thread lock

        //Perform registration
        this.beanDefinitionMap.put(beanName, beanDefinition);

        //Replace the original with a new list
        List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
        updatedDefinitions.addAll(this.beanDefinitionNames);
        updatedDefinitions.add(beanName);
        this.beanDefinitionNames = updatedDefinitions;

        //Check the collection of singleton bean names
        if (this.manualSingletonNames.contains(beanName)) {
            Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
            updatedSingletons.remove(beanName);
            this.manualSingletonNames = updatedSingletons;
        }
    }
}

Let's first trace the internal implementation of the hasBeanCreationStarted() method:

//AbstractBeanFactory.class
protected boolean hasBeanCreationStarted() {
    return !this.alreadyCreated.isEmpty();
}

alreadyCreated is a collection defined in AbstractBeanFactory to store the names of all beans that have been created (not registered, but created). The collection is checked when beans are created and retrieved.

In the registration phase of processors bean and iocConfig bean, the collection is empty, and this method returns false. Note, however, that during the registration phase of the person bean, the iocConfig bean has been created, so the collection is not empty, and this method returns true.

//Create a new list
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
//The old list is added to the new list as a whole
updatedDefinitions.addAll(this.beanDefinitionNames);
//Add beanName to the new list
updatedDefinitions.add(beanName);
//replace
this.beanDefinitionNames = updatedDefinitions;

bean Definition Names is a list defined in DefaultListable BeanFactory to store the names of all registered beans.

if (this.manualSingletonNames.contains(beanName)) {
    //Create a new collection and put the manual Singleton Names collection into the new collection
    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
    //Delete the bean name
    updatedSingletons.remove(beanName);
    //replace
    this.manualSingletonNames = updatedSingletons;
}

Manual Singleton Names is a collection defined in DefaultListable BeanFactory to hold the name of a single bean that has been created. Because it is singleton, the existence of renamed beans is not allowed, which may be the reason why Spring uses collections here.

Spring's memory optimization is accurate to the size of each list and collection's capacity.

In the case of this if criterion, it must be that the bean has not been created, so if the bean has been placed in this collection, it needs to be deleted. Normally, the bean registration process should not go into executing this code fragment.

2.4

Look at the fragments of the above methods:

if (existingDefinition != null || containsSingleton(beanName)) {
    resetBeanDefinition(beanName);
}

The existingDefinition in the judgment condition is a BeanDefinition defined above:

BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);

Generally speaking, it's equal to null.

The containsSingleton(...) method is implemented in DefaultSingleton Bean Registry:

//DefaultSingletonBeanRegistry.class
public boolean containsSingleton(String beanName) {
    return this.singletonObjects.containsKey(beanName);
}

As mentioned in the previous article, singletonObjects are the map object that ultimately saves the singleton bean.

To sum up, this judgment condition can be understood as: if the bean has been registered or created, it returns true, and if none has been created, it returns false. So the normal registration process does not perform the resetBeanDefinition(...) method.

As for the resetBeanDefinition(...) method, let's not expand on it. Generally speaking, this method will recreate the bean internally.

So far, reader has been created and processors used by Spring have been registered. The DefaultListableBeanFactory.registerBeanDefinition method is very important, which is also used for the registration of config bean s and person bean s.

2.5

Back to the origin:

//AnnotationConfigApplicationContext.class
public AnnotationConfigApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this); //2.4
}

ClassPathBean Definition Scanner is not used in this example for the time being, because there is no way to scan the package path to get the bean. So here's a brief introduction to the creation of ClassPathBean Definition Scanner.

Its construction method has multiple invocations, and the final logical code is as follows:

//ClassPathBeanDefinitionScanner.class
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
            Environment environment, @Nullable ResourceLoader resourceLoader) {
    //Parametric non-air validity
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

    //The registry here is Annotation Config Application Context
    this.registry = registry;

    //Filtering strategy of Spring scanning package path
    //useDefaultFilters = true
    if (useDefaultFilters) {
        registerDefaultFilters();
    }
    //Store the environment object, where a Standard Environment object is created using a parametric constructor
    setEnvironment(environment);
    //Save in resourceLoader, here Annotation Config Application Context
    setResourceLoader(resourceLoader);
}

[Not for the time being]

To Be Continued ...

Posted by Z3RatuL on Thu, 21 Mar 2019 01:18:53 -0700