Analysis of SpringBoot configuration class

This article starts with WeChat official account of vivo Internet technology.
Link: https://mp.weixin.qq.com/s/NvPO5-FWLiOlrsOf4wLaJA
By Shi Zhengxing

As a very popular open-source framework in Java field, SpringBoot integrates a large number of commonly used third-party library configurations. In Spring Boot applications, these third-party libraries can almost be used out of the box with zero configuration. Most Spring Boot applications only need a very small amount of configuration code, and developers can focus on business logic more. Spring Boot is quick to start, but if you need some special customization of business scenarios in your project, or even customization of the source code, then it is necessary to understand the principle. Only when you fully understand the source code and know the working principle of the bottom layer of the framework, can you modify / extend the original mechanism of the source code and so on. This article introduces how SpringBoot parses configuration classes and integrates third-party configuration.

1, Introduction to basic concepts

In spring boot, Java Config is recommended to replace the traditional XML method to introduce beans. This paper analyzes how spring boot parses these configuration classes, injects our customized and spring boot provided beans into the container. The spring boot version is based on 2.1.7.RELEASE.

// Usually a spring boot project contains such a main configuration class, which is located under the root package of our project. By starting this main method, we can start our project
// Next, we will analyze the function of @ SpringBootApplication annotation. In the second section, we will analyze the run method. In the run method, we will analyze the configuration class
@SpringBootApplication
public class SpringbootApplication {
    public static void main(String[] args) {
       SpringApplication.run(SpringbootApplication.class, args);
    }
}
// Click @ SpringBootApplication to find that it is actually composed of three core annotations, which are explained below
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

1. @ SpringBootConfiguration annotation

// Click here to find that it is actually a @ Configuration annotation. When spring boot parses it, it will know that this is a Configuration class, and it will introduce some bean s to the container
// A class marked by @ Configuration, equivalent to a applicationContext.xml file
// @Configuration is actually a @ Component annotation
@Configuration
public @interface SpringBootConfiguration {

}

2. @ EnableAutoConfiguration annotation

// Combined with the following @ AutoConfigurationPackage annotation, it is found that the @ EnableAutoConfiguration annotation introduces two bean s to the container through the @ Import annotation,
// AutoConfigurationImportSelector and autoc onfigurationPackages.Registrar Through these two classes, more classes can be introduced into the container
// Next, I will introduce the use of @ Import annotation
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

@Import annotation

In the native spring framework, there are three ways to assemble components

  1. Use @ Component annotation, spring 2.5+
  2. Use Configuration classes @ Configuration and @ Bean, Spring3.0+
  3. Using modules to assemble @ EnableXXX and @ Import, Spring3.1+

If you want to register more beans, it is not convenient to use 1) 2) two methods. You can use the module assembly function provided by Spring. You can complete the effect of component assembly by annotating @ Enable for the configuration class and @ Import for the annotation. Next, you can explain the use of @ EnableXXX and @ Import through an example.

// Step 1) 
// Create entity classes of several animals, such as Cat, Bird, Chicken, Dog, Duck, Pig, Sheep, Snake, Tiger
// The bean s are then injected into the container through various uses of @ Import
@Data
public class Cat {
    private String name;
}
// Step 2) add our customized @ EnableAnimal annotation to the main configuration class, which means that the above animal beans will be imported into the container through @ EnableAnimal
// @EnableAnimal can be compared with the @ EnableAutoConfiguration annotation we mentioned above. The two functions are similar
@SpringBootApplication
@EnableAnimal
public class SpringbootApplication {
    public static void main(String[] args) {
       SpringApplication.run(SpringbootApplication.class, args);
    }
}
// Step 3) 
// @EnableAnimal is shown below. This is our custom annotation. The core of this annotation is to declare the @ Import annotation on it
// @The Import annotation can pass in four types: normal class, configuration class, implementation class of ImportSelector, and implementation class of importbeandefinitionregister
// Here are four types of usage of @ Import annotation Import
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Dog.class, AnimalRegisterConfiguration.class, AnimalImportSelector.class, 
                                                       AnimalImportBeanDefinitionRegistar.class})
public @interface EnableAnimal {
}
//1) direct @ Import({Dog.class }), there will be the bean of dog in the container after the container is started. Note that the class of dog is not injected through @ Component
// 2) @Import ({AnimalR egisterConfiguration.class }) can also be imported in this form of import configuration class

@Configuration
public class AnimalRegisterConfiguration {

    @Bean
    public Cat cat(){
        return new Cat("cat");
    }
}
// 3) @Import({AnimalImportSelector.class }), by returning a Spring array, you can easily specify multiple beans in the array
// The above AutoConfigurationImportSelector is just this way. Spring boot imports MybatisAutoConfiguration to the container
// Automatic configuration classes are imported in this way. In Section 2, configuration class parsing will talk about this
public class AnimalImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {Tiger.class.getName()};
    }
}
// 4) @Import({AnimalImportBe anDefinitionRegistar.class }), inject Bean into registry by manual encoding
// We're talking about autoc onfigurationPackages.Registrar That's how it works

public class AnimalImportBeanDefinitionRegistar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("snake", new RootBeanDefinition(Snake.class));
    }
}

3. @ ComponentScan annotation

This annotation is equivalent to that in XML< context:component-scan >It will scan the classes identified with @ Controller, @ Service, @ Repository, @ Component annotation from the defined scan package path (the default is the package and its subpackages where the Spring boot master configuration is located) to the Spring container.

// We can see that the @ ComponentScan annotation specifies two Filter filter conditions. It is an extension mechanism provided by SpringBoot, which enables us to
// Register some custom component filters with IOC container to filter some beans during package scanning
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
// This is the method of TypeExcludeFilter. You can see that it is to get all typeexcludefilters in the container and traverse them to filter some unwanted beans
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
    if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
  Collection<typeexcludefilter> delegates
                    = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
  for (TypeExcludeFilter delegate : delegates) {
      if (delegate.match(metadataReader, metadataReaderFactory)) {
    return true;
      }
  }
    }
    return false;
}
// We can customize a TypeExcludeFilter. Its function is to filter out the User bean and prevent it from being injected into the container
@Component
public class MyTypeExcludeFilter extends TypeExcludeFilter {

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return User.class.getName().equals(metadataReader.getClassMetadata().getClassName());
    }
}

@Component
public class User {
    private String name;
}
// You can see that AutoConfigurationExcludeFilter filters the autoconfiguration class, because the autoconfiguration class xxxAutoConfiguration is passed by
// The above AutoConfigurationImportSelector is imported. It is unnecessary to import it twice
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
    }
}

4. @ Conditional comment

We say that the spring boot convention is larger than the configuration. It imports some components into the container through some xxxAutoConfiguration. If you need but do not configure the view parser, spring boot will provide its default view parser, which simplifies the configuration. If you define a view parser yourself, which one will spring boot inject into the container? Look at the default injected view parser code below, and find that there is a @ ConditionalOnMissingBean annotation on it, which means that if there is no such view parser in the container, the container will inject you one. If there is one in the container, it will not inject it.

// @ConditionalOnMissingBean is an implementation class through @ Conditional annotation and Condition interface( OnBeanCondition.class )To achieve this effect
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

We can also implement a custom condition annotation ourselves.

// Define the implementation class MyCondition of a Condition interface, and judge whether the specified conditions are met through its matches method
public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Get the value corresponding to the value specified in the annotation myconditionannotation. If there is no such value, the condition is not met
        String[] value = (String[]) metadata.getAnnotationAttributes(MyConditionAnotation.class.getName()).get("value");
        for (String property : value){
            if(StringUtils.isEmpty(context.getEnvironment().getProperty(property))){
                return false;
            }
        }
        return true;
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(MyCondition.class)
public @interface MyConditionAnotation {
    String[] value() default {};
}
// Inject A type A Bean into the container through @ Component, provided that the values corresponding to key1 and key2 specified by @ myconditionannotation annotation are configured
@Component
@MyConditionAnotation({"key1", "key2"})
public class A {
}

5,SpringFactoriesLoader

Similar to Java's SPI and Dubbo's SPI mechanism, SpringBoot also provides a mechanism by reading META-INF/spring.factories Files (these files may exist in multiple jar packages in the classpath) to load some preconfigured classes, and the core mechanism comes from spring factors loader. spring.factories The file must be in the form of properties, where key is the fully qualified name of the interface or abstract class, and value is the comma separated list of fully qualified class names of the implementation class.

// Let's take a look at the AutoConfigurationImportSelector described in the @ Import annotation above. We say that we can Import spring boot to provide those
// The following is the selectImport method of AutoConfigurationImportSelector described in the Import annotation
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
     // Ignore some other code
     // The meaning here is to load meta-inf through spring factors loader/ spring.factories Those xxxAutoConfiguration configured in
     // And put it into the String array to return
     AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                       annotationMetadata);
     return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

// Ignore the intermediate procedure call, which will be analyzed later. Next, go here and load the auto configuration class through springfactoryesloader
protected List<string> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
     // See the following analysis for specific calls
     List<string> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                    getBeanClassLoader());
     return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
     return EnableAutoConfiguration.class;
}
public static List<string> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    // org.springframework.boot.autoconfigure.EnableAutoConfiguration
    String factoryClassName = factoryClass.getName();
    // Get a map in the form of < key, list < string > & gt;. getOrDefault
    // org.springframework.boot.autoconfigure.EnableAutoConfiguration Corresponding implementation class set
    // Note that not only the automatic configuration classes are loaded, but also the implementation classes configured by the listener and initializer are loaded. Here, they are uniformly loaded and put into the cache
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

// Meta-inf with cache function from each jar package/ spring.factories The implementation class is loaded in the file. A key may contain multiple implementations
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        // Direct return in cache
        return result;
    }

    try {
        // FACTORIES_RESOURCE_LOCATION value is META-INF/spring.factories
        Enumeration<url> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                                       ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // Get key
                String factoryClassName = ((String) entry.getKey()).trim();
                // Separate value commas to obtain specific implementation classes
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    // Put it into result
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        // See the figure below for the returned result
        return result;
    }
}

You can see that many auto configuration classes are returned, including aopaconfiguration, RabbitAutoConfiguration, and mybatisautoconfiguration (not truncated in the figure)

6,BeanFactoryPostProcessor

  1. BeanFactoryPostProcessor, BeanFactory postprocessor, is an extension point for BeanFactory. Spring will load beandefinition after BeanFactory initialization. However, beandefinition can be modified or added before bean creation.
  2. BeanDefinitionRegistryPostProcessor, a sub interface of BeanFactoryPostProcessor, is an extension point for BeanFactory, that is, Spring will call it before calling BeanFactoryPostProcessor. We will focus on the following
  3. ConfigurationClassPostProcessor is the implementation class of the interface. SpringBoot uses it to parse configuration classes and encapsulate them into BeanDefinition containers.
  4. BeanPostProcessor is an extension point for Bean, that is, Spring will process Bean before and after Bean initialization, and AOP and dependency injection are implemented through BeanPostProcessor.

The following is a customized BeanFactoryPostProcessor and BeanPostProcessor. It is found that BeanFactoryPostProcessor can add a new Bean to the container or modify the original Bean definition. BeanPostProcessor can modify the property values of the created Bean.

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

        // Add a BeanDefinition to the container
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(Chicken.class);
        registry.registerBeanDefinition("beanFactoryPostProcessor-Chicken", beanDefinition);

        // Modify the original BeanDefinition in the container
        BeanDefinition snake = registry.getBeanDefinition("snake");
        snake.setLazyInit(true);
    }
}
@Component
public class CatBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Cat){
            Cat cat = (Cat) bean;
            cat.setName("changeNameCat");
        }
        return bean;
    }
}

2, Overview of SpringBoot startup process

The first section is some knowledge points that will be used in the spring boot parsing auto configuration class. Let's see the specific process of spring boot parsing configuration class. The above figure is the spring boot startup flowchart. In step 5 of the refreshContext, the postProcessBeanDefinitionRegistry method of the container's BeanFactoryPostProcessor will be called. One of them is the ConfigurationClassPostProcessor, which is set into the container when the ConfigurableApplicationContext is created, as shown below.

// To create a ConfigurableApplicationContext, the default is to create a normal Servlet Web container, which is the following
// Create by reflection will go to its default constructor
public AnnotationConfigServletWebServerApplicationContext() {
    // If you go in here, you will go to the following code
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

// Come here
registerAnnotationConfigProcessors(registry, null);

// Come here
// Inject a ConfigurationClassPostProcessor into the container, which is BeanFactoryPostProcessor
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}

// Inject an Autowired annotation BeanPostProcessor into the container, which is used to solve dependency injection
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));
}

3, Configuration class resolution

As mentioned above in step 5 of refreshcontext, BeanFactoryPostProcessor in the container will be called

The postProcessBeanDefinitionRegistry method of. One of them is ConfigurationClassPostProcessor, which is the entry for us to parse the automatic configuration class. Next, we will analyze its postProcessBeanDefinitionRegistry method.

1. Overview of configuration class resolution process

@Override  
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {  
    // Delete some non critical code
    processConfigBeanDefinitions(registry);  
}

The following processConfigBeanDefinitions method corresponds to steps 1, 2, 3, 4, and 5 in the above figure. Steps 4 and 5 are relatively long, which are separated for analysis.

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {  
    List<beandefinitionholder> configCandidates = new ArrayList<>(); 
    // Get the registered bean names in the container, as shown in the figure below. Note that these bean definitions in the container are added by the container during initialization
    // It's not the bean definition of our business code. This code is actually coherent. It's separated for the convenience of annotation pictures
    String[] candidateNames = registry.getBeanDefinitionNames();  

    for (String beanName : candidateNames) {  
  // Get BeanDefinition
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);  
  // Determine whether the configurationClass property of this BeanDefinition is full or lite. If it is considered that it has been processed, it will be empty by default the first time,
  // Go down the branch
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||  
            ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {  
            // Print log record
        } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
      // 1) Next, we will analyze the checkConfigurationClassCandidate method. From the method name, we can guess whether the class is a configuration class
      // Configuration class means that it will introduce bean s into the container. This method mainly depends on whether there is @ configuration annotation in the class's meta information
      // Do you have @ Component annotation, @ ComponentScan, @ Import, @ ImportResource annotation, and @ Bean method
      // Construct a BeanDefinitionHolder and put it into configCandidates
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));  
        }  
    }  
  
    // Return immediately if no @Configuration classes were found  
    // In the above figure, seven bean definitions have been introduced by default. Through the above detection, it is found that by default, there is a qualified configuration class, namely our main configuration class
    // configCandidates is one of them, which is spring boot application
    if (configCandidates.isEmpty()) {  
        return;  
    }  
  
    // Sort by previously determined @Order value, if applicable 
    // sort
    configCandidates.sort((bd1, bd2) -> {  
        int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());  
        int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());  
        return Integer.compare(i1, i2);  
    });  
  
    // Delete some codes
  
    // Parse each @Configuration class  
    // Configuration class resolution tool
    ConfigurationClassParser parser = new ConfigurationClassParser(  
                                this.metadataReaderFactory, this.problemReporter, this.environment,  
                                this.resourceLoader, this.componentScanBeanNameGenerator, registry);  
    // Pending set
    Set<beandefinitionholder> candidates = new LinkedHashSet<>(configCandidates);  
    // Collections processed
    Set<configurationclass> alreadyParsed = new HashSet<>(configCandidates.size());  
    // Cycle until candidates.isEmpty()
    do {  
  // Start parsing here, corresponding to step 4
        parser.parse(candidates);  
        parser.validate();  
        // Take out some configuration classes set from step 4
        Set<configurationclass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());  
        configClasses.removeAll(alreadyParsed);  
  
        // Delete some codes
  // The bean definition will also be loaded here, corresponding to step 5 in the figure
        this.reader.loadBeanDefinitions(configClasses);  
        alreadyParsed.addAll(configClasses);  
   }  
   while (!candidates.isEmpty());  
}

2. Check whether it is a configuration class

In the Configuration class resolution flow chart, the second step is to get the registered beandefinitions in the container, put them into the candidateNames, and then traverse these beandefinitions in turn to determine whether they have been processed. If they have been processed, it doesn't matter. Otherwise, the checkConfigurationClassCandidate method is used to determine whether they are Configuration classes. By reading this code, we find that if a class has @ Configuration annotation, or @ Component, @ ComponentScan, @ Import, @ ImportResource annotation, or @ Bean annotation, it is considered a Configuration class. By default, when we get here, only one candidateName will match. It is our main Configuration class, that is, spring bootapplication Bean.

ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory);

public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, 
                                                       MetadataReaderFactory metadataReaderFactory) {  
  
    String className = beanDef.getBeanClassName();  
    // Gets the name of the next class. If the class name is empty or the class is a factory class
    if (className == null || beanDef.getFactoryMethodName() != null) {  
        return false;  
    }  
  
    // Get metadata information of class
    AnnotationMetadata metadata; 
    // Only one of the seven candidate names in the above figure is annotatebeandefinition, and the rest return false
    if (beanDef instanceof AnnotatedBeanDefinition &&
                  className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {  
        // Can reuse the pre-parsed metadata from the given BeanDefinition... 
  // springbootApplication comes here
        metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();  
    }  
    else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {  
        // Check already loaded Class if present...  
        // since we possibly can't even load the class file for this Class.  
  Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();  
        metadata = new StandardAnnotationMetadata(beanClass, true);  
    }  
    else {  
        try {  
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);  
      // Read the metadata information of the class, including annotations and other information
            metadata = metadataReader.getAnnotationMetadata();  
        }  
        catch (IOException ex) {  
            return false;  
        }  
    }  
  
    // metadata.isAnnotated(Configuration.class.getName()), this is to determine whether there is @ configuration annotation on the class
    if (isFullConfigurationCandidate(metadata)) {  
  // If this property is set to true, it will be marked as processed
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);  
    } else if (isLiteConfigurationCandidate(metadata)) {  
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);  
    }  
    else {  
  // The remaining six return false
        return false;  
    }  
  
    // It's a full or lite configuration candidate... Let's determine the order value, if any.  
    Integer order = getOrder(metadata);  
    if (order != null) { 
       // Get @ Order information on the following class
       beanDef.setAttribute(ORDER_ATTRIBUTE, order);  
    }  
    return true;  
}
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {  
    // Do not consider an interface or an annotation...  
    if (metadata.isInterface()) {  
        return false;  
    }  
  
    // Any of the typical annotations found?  
    for (String indicator : candidateIndicators) {  
  // Judge whether there are any comments on the next class
        if (metadata.isAnnotated(indicator)) {  
           return true;  
        }  
    }  
  
    // Finally, let's look for @Bean methods...  
    try {  
        // Determine if @ Bean is available
        return metadata.hasAnnotatedMethods(Bean.class.getName());  
    }  
    return false;  
  }  
}

private static final Set<string> candidateIndicators = new HashSet<>();  
  
static {  
    candidateIndicators.add(Component.class.getName());  
    candidateIndicators.add(ComponentScan.class.getName());  
    candidateIndicators.add(Import.class.getName());  
    candidateIndicators.add(ImportResource.class.getName());  
}

3. Step 4 Analysis

public void parse(Set<beandefinitionholder> configCandidates) {  
    // Delete part of the code. configCandidates here is the main configuration class represented by springBootApplication
    for (BeanDefinitionHolder holder : configCandidates) {
        // Get BeanDefinition
        BeanDefinition bd = holder.getBeanDefinition();  
        // Our SpringBootApplication will come this way. Let's analyze this first
        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); 
    }  
    // Pay attention to (* *). The DeferredImportSelector will be processed here. The AutoConfigurationImportSelector we mentioned earlier is processed here
    // Import xxxAutoConfiguration to container
    this.deferredImportSelectorHandler.process();  
}

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {  
     processConfigurationClass(new ConfigurationClass(metadata, beanName));  
}

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {  
     // Determine whether to resolve this configuration class according to the conditions marked by the @ Conditional annotation on the current class
     if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {  
        return;  
     }  
     // Use configClass as the key to obtain. You can't get it for the first time. Follow the logic below
     ConfigurationClass existingClass = this.configurationClasses.get(configClass);  
     if (existingClass != null) {  
         if (configClass.isImported()) {  
             if (existingClass.isImported()) {  
                 existingClass.mergeImportedBy(configClass);  
             }  
             // Otherwise ignore new imported config class; existing non-imported class overrides it.  
             return;  
         }  
         else {  
             // Explicit bean definition found, probably replacing an import.  
             // Let's remove the old one and go with the new one.  this.configurationClasses.remove(configClass);  
             this.knownSuperclasses.values().removeIf(configClass::equals);  
          }  
      }  
  
      // Recursively process the configuration class and its superclass hierarchy.
      // In fact, there is nothing to be done in this step. The focus is on the next step
      SourceClass sourceClass = asSourceClass(configClass);  
      do {  
    // Here is the key point, which is divided into 8 steps. Take a separate section for analysis
    // b) doProcessConfigurationClass
          sourceClass = doProcessConfigurationClass(configClass, sourceClass);  
      }  
      while (sourceClass != null);  
     // Put it into the configuration classes
      this.configurationClasses.put(configClass, configClass);  
}
// The above asSourceClass actually encapsulates a SourceClass object
public SourceClass(Object source) {  
    this.source = source;  
    if (source instanceof Class) {  
        this.metadata = new StandardAnnotationMetadata((Class<?>) source, true);  
    }  
    else {  
        this.metadata = ((MetadataReader) source).getAnnotationMetadata();  
    }  
}

The following doprocess configuration class is divided into eight small steps to analyze, corresponding to four A-H steps

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)  
      throws IOException {  
    // Determine whether there is @ Component annotation on this class 
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {  
        // Recursively process any member (nested) classes first  
  // If there is, traverse its inner class, and then call doProcessConfigurationClass to recursively process
        processMemberClasses(configClass, sourceClass);  
    }  
  
    // Process any @PropertySource annotations
    // Handle the PropertySource annotation, which has been analyzed in the previous explanation of property configuration, that is, load the property file corresponding to the annotation into the Environment
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(  
                                               sourceClass.getMetadata(), PropertySources.class,  
                                               org.springframework.context.annotation.PropertySource.class)) {  
        if (this.environment instanceof ConfigurableEnvironment) {  
            processPropertySource(propertySource);  
        }  
    }  
  
    // Process any @ComponentScan annotations 
    // Process the @ ComponentScan annotation to register the bean s under the package specified in it into the framework
    Set<annotationattributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(  
                                         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);  
    if (!componentScans.isEmpty() &&
             !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {  
        for (AnnotationAttributes componentScan : componentScans) {  
            // The config class is annotated with @ComponentScan -&gt; perform the scan immediately  
            Set<beandefinitionholder> scannedBeanDefinitions =  
                         this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());  
            // Check the set of scanned definitions for any further config classes and parse recursively if needed  
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {  
                 BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();  
                 if (bdCand == null) {  
                     bdCand = holder.getBeanDefinition();  
                 }  
                 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {  
                     parse(bdCand.getBeanClassName(), holder.getBeanName());  
                 }  
            }  
        }  
    }  
  
    // Process any @Import annotations  
    // Handling Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), true);  
  
    // Process any @ImportResource annotations  
    // Handle @ ImportResource annotation. You can specify xml file through it. BeanFactory will read the xml file and register the bean
    AnnotationAttributes importResource =  
                           AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);  
    if (importResource != null) {  
        String[] resources = importResource.getStringArray("locations");  
        Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");  
        for (String resource : resources) {  
            String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);  
            configClass.addImportedResource(resolvedResource, readerClass);  
        }  
    }  
  
    // Process individual @Bean methods  
    // Process the @ Bean annotated method in our class and add it to the Bean method of configClass
    Set<methodmetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);  
    for (MethodMetadata methodMetadata : beanMethods) {  
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));  
    }  
  
    // Process default methods on interfaces  
    // Handle the default method of the interface, traverse the interface of this class, judge whether there is a non abstract method annotated with @ Bean, and add it to the beanMethod of configClass
    processInterfaces(configClass, sourceClass);  
  
    // Process superclass, if any  
    // Recursively process the parent class. The upper level method of the parent class will recursively process
    if (sourceClass.getMetadata().hasSuperClass()) {  
       // Judge that the parent class is not null, is not in knownSuperclasses, and does not start with Java
       String superclass = sourceClass.getMetadata().getSuperClassName();  
       if (superclass != null && !superclass.startsWith("java") &&
                                              !this.knownSuperclasses.containsKey(superclass)) {  
           this.knownSuperclasses.put(superclass, configClass);  
           // Superclass found, return its annotation metadata and recurse  
           return sourceClass.getSuperClass();  
       }  
    }  
  
    // No superclass -&gt; processing is complete  
    return null;  
}

(1) Handle inner classes

if (configClass.getMetadata().isAnnotated(Component.class.getName())) {  
    // Recursively process any member (nested) classes first  
    processMemberClasses(configClass, sourceClass);  
}


 // Register member (nested) classes that happen to be configuration classes themselves.
 private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {  
     Collection<SourceClass> memberClasses = sourceClass.getMemberClasses(); 
     // Determine whether there is an internal class. If not, don't handle it directly
     if (!memberClasses.isEmpty()) {  
         List<SourceClass> candidates = new ArrayList<>(memberClasses.size());  
         for (SourceClass memberClass : memberClasses) {  
       // It's also easy to determine whether a Configuration class is a Configuration class. We analyzed it before to determine whether there is @ Configuration annotation, @ Import, @ ImportResource on the class
       // @Component, @ ComponentScan, and @ Bean annotation methods
             if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&  
                     !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {  
      // Add to candidates and sort
                  candidates.add(memberClass);  
             }  
         }  
         OrderComparator.sort(candidates);  
         for (SourceClass candidate : candidates) {     
       // Prevent introduction of A prevent introduction of A into B,B into A
             if (this.importStack.contains(configClass)) {  
                this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));  
             }  
             else {  
                this.importStack.push(configClass);  
                try {  
        // Put it into the stack and traverse to handle these configuration classes. It is also recursive. Call the previous doProcessConfigurationClass to handle this configuration class
                    processConfigurationClass(candidate.asConfigClass(configClass));  
                }  
                finally {  
                    this.importStack.pop();  
                }  
             }  
         }  
    }  
}

(2) Process @ PropertySource annotation

@SpringBootApplication  
@PropertySource({"demo.properties"})  
public class Springboot2Application {
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(  
                                                sourceClass.getMetadata(), PropertySources.class,  
                                                org.springframework.context.annotation.PropertySource.class)) {  
    if (this.environment instanceof ConfigurableEnvironment) {  
  // I won't go in here. It is mainly to read the file specified by @ PropertySource annotation and encapsulate it into a property set and put it into the environment
        processPropertySource(propertySource);  
    }  
}

(3) Process @ ComponentScan annotation

Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(  
                                       sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);  
if (!componentScans.isEmpty() &&  
          !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {  
    for (AnnotationAttributes componentScan : componentScans) {  
        // The config class is annotated with @ComponentScan -> perform the scan immediately  
  // Let's analyze the parse method first
        Set<BeanDefinitionHolder> scannedBeanDefinitions =  
                     this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());  
        // Check the set of scanned definitions for any further config classes and parse recursively if needed  
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {  
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();  
            if (bdCand == null) {  
                bdCand = holder.getBeanDefinition();  
            }  
      // If it is a configuration class, recursively handle it again
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {  
                parse(bdCand.getBeanClassName(), holder.getBeanName());  
            }  
        }  
    }  
}

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {  
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,  
                           componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);  
  
    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");  
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); 
    // Set a bean name generator. The default is to use org.springframework.beans.factory.support.BeanNameGenerator
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :  
                                                                      BeanUtils.instantiateClass(generatorClass));  
  
    // It's the default
    ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");  
    if (scopedProxyMode != ScopedProxyMode.DEFAULT) {  
        scanner.setScopedProxyMode(scopedProxyMode);  
    }  
    else {  
        Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");  
  // Understanding is a metadata parser
        scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));  
    }  
  
    // Set the scan resource mode under * * / *. class
    scanner.setResourcePattern(componentScan.getString("resourcePattern"));  
  
    // Add IncludeFilter and ExcludeFilter
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {  
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {  
            scanner.addIncludeFilter(typeFilter);  
        }  
    }  
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {  
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {  
            scanner.addExcludeFilter(typeFilter);  
        }  
    }  
    // Set whether to lazy load
    boolean lazyInit = componentScan.getBoolean("lazyInit");  
    if (lazyInit) {  
        scanner.getBeanDefinitionDefaults().setLazyInit(true);  
    }  
  
    // Resolve the scanned package path and add it to basePackages
    Set<String> basePackages = new LinkedHashSet<>();  
    String[] basePackagesArray = componentScan.getStringArray("basePackages");  
    for (String pkg : basePackagesArray) {  
        String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),  
                                                 ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);  
        Collections.addAll(basePackages, tokenized);  
    }  
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {  
  // Resolve the package of basePackageClasses and add it to basePackages
        basePackages.add(ClassUtils.getPackageName(clazz));  
    }  
     // If it is empty, add the package of the class where the annotation is declared to basePackages
    if (basePackages.isEmpty()) {  
  // Generally, our main configuration class does not declare the path of package scanning, so the package where the main configuration class is located will be added here
        basePackages.add(ClassUtils.getPackageName(declaringClass));  
    }  
  
    // Add an ExcludeFilter to skip the class that declares the annotation
    scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {  
       @Override  
       protected boolean matchClassName(String className) {  
           return declaringClass.equals(className);  
       }  
    });  
    return scanner.doScan(StringUtils.toStringArray(basePackages));  
}

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {  
     Assert.notEmpty(basePackages, "At least one base package must be specified");  
     Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();  
     // Traverse all package paths
     for (String basePackage : basePackages) {  
   // Get all the qualified beandefinitions under the package, and then traverse the process, which will be analyzed as follows
         Set<BeanDefinition> candidates = findCandidateComponents(basePackage);  
         for (BeanDefinition candidate : candidates) {  
             ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);  
             candidate.setScope(scopeMetadata.getScopeName());  
       // Generating beanName from beanNameGenerator
             String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
       // These two if judgment logics are relatively simple, that is, set some Lazy and DependsOn properties
             if (candidate instanceof AbstractBeanDefinition) {  
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);  
             }  
             if (candidate instanceof AnnotatedBeanDefinition) {  
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);  
             }  
       // Here is to check whether the bean definition has been defined before
             if (checkCandidate(beanName, candidate)) {  
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);  
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, 
                                                      definitionHolder, this.registry);  
                // Add the bean definition to the collection and register it in the container
                beanDefinitions.add(definitionHolder);  
                registerBeanDefinition(definitionHolder, this.registry);  
             }  
        }  
    }  
    return beanDefinitions;  
}

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {  
     Set<BeanDefinition> candidates = new LinkedHashSet<>();  
     try {  
         // Scan the class file under the specified package path and its subpackages, and encapsulate it as a Resource object
   // classpath*:com/lwh/springboot/**/*.class
   String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +  
                                          resolveBasePackage(basePackage) + '/' + this.resourcePattern;  
         Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);  
   
         for (Resource resource : resources) {  
             if (resource.isReadable()) {  
                try {  
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);  
                    if (isCandidateComponent(metadataReader)) {  
                         ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);  
                         sbd.setResource(resource);  
                         sbd.setSource(resource);  
                         if (isCandidateComponent(sbd)) {  
                             candidates.add(sbd);  
                         }  
                    }  
                }  
            }  
         }
    }
    return candidates;  
}
// filter through several filters previously set
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {  
    for (TypeFilter tf : this.excludeFilters) {  
        if (tf.match(metadataReader, getMetadataReaderFactory())) {  
            return false;  
        }  
    }  
    for (TypeFilter tf : this.includeFilters) {  
        if (tf.match(metadataReader, getMetadataReaderFactory())) {  
            return isConditionMatch(metadataReader);  
        }  
    }  
    return false;  
}

(4) Process @ Import annotation

// Process any @Import annotations  
// getImports method is to recursively scan all the annotations on configClass, and put the value of @ Import annotation annotation into importCandidates, as shown in the following figure
processImports(configClass, sourceClass, getImports(sourceClass), true);
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,  
                                     Collection<SourceClass> importCandidates, boolean checkForCircularImports) {  
  
    if (importCandidates.isEmpty()) {  
        return;  
    }  
  
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {  
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));  
    }  
    else {  
        this.importStack.push(configClass);  
        try {  
            for (SourceClass candidate : importCandidates) {  
    // Traversal judgment type in turn
    // One of them is this type ,@Import (AutoConfigu rationImportSelector.class )
    // This is the principle of auto configuration. Import these classes
                if (candidate.isAssignable(ImportSelector.class)) {  
                    // Candidate class is an ImportSelector -> delegate to it to determine imports  
                    Class<?> candidateClass = candidate.loadClass();  
        // Instantiate and call methods of xxxAware and inject related properties
                    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);  
                    ParserStrategyUtils.invokeAwareMethods(  
                                            selector, this.environment, this.resourceLoader, this.registry); 
        // It is of type DeferredImportSelector
                    if (selector instanceof DeferredImportSelector) {  
                  // deferredImportSelectors = new ArrayList<>()
      // In this case, the two parameters will be encapsulated and added to the deferredImportSelectors for later processing
      // After adding to deferredImportSelectors, the specific processing is this.deferredImportSelectorHandler.process();
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);  
                    }  
                    else {  
      // If not, get the class name array imported by @ Import
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());  
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); 
      // And then recursively
                        processImports(configClass, currentSourceClass, importSourceClasses, false);  
                    }  
                }  
    // @Import(AutoC onfigurationPackages.Registrar.class ), the annotation above our main configuration class is this type
    // This is used to import the BeanDefinition under the package where the main configuration class is located and its subpackages
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {  
                     // Candidate class is an ImportBeanDefinitionRegistrar ->  
                     // delegate to it to register additional bean definitions  
                     Class<?> candidateClass = candidate.loadClass();  
                     ImportBeanDefinitionRegistrar registrar =  
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);  
                     ParserStrategyUtils.invokeAwareMethods(  
                                               registrar, this.environment, this.resourceLoader, this.registry);  
         // Here we take these two parameters as key and put value into a map
               // this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
                     configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());  
                }  
                else {  
                     // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->  
                     // process it as an @Configuration class  
               this.importStack.registerImport(  
                                     currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());  
         // Treat it as a common class, judge whether it is a configuration class, and deal with it recursively
                     processConfigurationClass(candidate.asConfigClass(configClass));  
                }  
         }  
      }  
      finally {  
         this.importStack.pop();  
      }  
   }  
}

(5) Process @ ImportSource annotation

// This is a common way of injecting XML in Spring
@SpringBootApplication  
@ImportResource("test.xml")  
public class Springboot2Application {
// Process any @ImportResource annotations
// You can use @ ImportResource annotation to specify xml file and import BeanDefinition
AnnotationAttributes importResource =  
                AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);  
if (importResource != null) {  
     String[] resources = importResource.getStringArray("locations");  
     Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");  
     for (String resource : resources) {  
   // namely test.xml
         String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);  
         configClass.addImportedResource(resolvedResource, readerClass);  
     }  
}

public void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {  
     // This is to put it into the map. This is to store it uniformly. In step 5, 4) import bean definition
     this.importedResources.put(importedResource, readerClass);  
}

private final Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>();

(6) How to handle @ Bean annotation

// Process individual @Bean methods  
// Get the Bean method in the current class
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);  
for (MethodMetadata methodMetadata : beanMethods) {  
     // This is also added to the set. See the following code. It is also processed in step 5, 3)
     configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));  
}

private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();

(7) Handle default methods

// For example, the main configuration class implements this interface
public interface ConfigurationInterface {

    @Bean
    default Pig pig(){
        return new Pig();
    }
}
 // Process default methods on interfaces  
 processInterfaces(configClass, sourceClass);

 // Register default methods on interfaces implemented by the configuration class.
 // This is also the recursive processing of its parent interface. Judge whether the default method in the parent interface is the @ Bean method
 private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {  
     for (SourceClass ifc : sourceClass.getInterfaces()) {  
         Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);  
         for (MethodMetadata methodMetadata : beanMethods) {  
             if (!methodMetadata.isAbstract()) {  
                 // Also in step 5, 3)
                 configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));  
             }  
         }  
         processInterfaces(configClass, ifc);  
     }  
}

(8) Recursively handle parent classes

// Process superclass, if any  
if (sourceClass.getMetadata().hasSuperClass()) {  
    String superclass = sourceClass.getMetadata().getSuperClassName();  
    if (superclass != null && !superclass.startsWith("java") &&  
                              !this.knownSuperclasses.containsKey(superclass)) {  
        this.knownSuperclasses.put(superclass, configClass);  
        // Superclass found, return its annotation metadata and recurse  
        return sourceClass.getSuperClass();  
    }  
}

As mentioned above, most of the methods in step 4 have been analyzed, but there is another important step that has not been analyzed. It is to import the automatic configuration classes such as xxxAutoConfiguration. The following analysis:

// When parsing @ Import annotation, our AutoConfigurationImportSelector is of type DeferredImportSelector, which means deferring Import
// Why delay import? Because AutoConfigurationImportSelector imports some default components to the container. If the container already has this type
// Therefore, it is intended to wait for the spring boot container to parse the user-defined beans first, and then import them only when it is found that they are not
// Some default components
if (selector instanceof DeferredImportSelector) {  
   // deferredImportSelectors = new ArrayList<>()
   // In this case, the two parameters will be encapsulated and added to the deferredImportSelectors for later processing
   // After adding to deferredImportSelectors, the specific processing is this.deferredImportSelectorHandler.process();
   this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);  
}
// Step 4 starts from here
public void parse(Set<beandefinitionholder> configCandidates) {  
    // Delete part of the code. configCandidates here is the main configuration class represented by springBootApplication
    for (BeanDefinitionHolder holder : configCandidates) {
        // Get BeanDefinition
        BeanDefinition bd = holder.getBeanDefinition();  
        // Our SpringBootApplication will come this way. Let's analyze this first
        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); 
    }  
  
    // Import xxxAutoConfiguration to the container. Let's analyze it now
    this.deferredImportSelectorHandler.process();  
}

// The above method will come here
public void processGroupImports() {  
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {  
   // Let's first analyze the getImports method
         grouping.getImports().forEach(entry -> {  
             ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());  
             try {  
                 processImports(configurationClass, asSourceClass(configurationClass),  
                 asSourceClasses(entry.getImportClassName()), false);  
             }  
         });  
    }  
}

public Iterable<group.entry> getImports() {  
    // The deferredImports here go through grouping.add (deferred import) added
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {  
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),  
                                                                       deferredImport.getImportSelector());  
    }  
    return this.group.selectImports();  
}

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { 
     // Let's first analyze the getAutoConfigurationEntry method
     AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)  
                     .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);  
     // Add to this autoConfigurationEntries
     this.autoConfigurationEntries.add(autoConfigurationEntry);  
     for (String importClassName : autoConfigurationEntry.getConfigurations()) {  
   // Next, traverse and put it into the map successively, map < string, annotation data > entries = new LinkedHashMap < >
         this.entries.putIfAbsent(importClassName, annotationMetadata);  
     }  
}
// This is the method of AutoConfigurationImportSelector class
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,  
                                                           AnnotationMetadata annotationMetadata) {  
    // Determine whether automatic configuration is supported
    if (!isEnabled(annotationMetadata)) {  
        return EMPTY_ENTRY;  
    }  
    // The attributes attribute is shown in the figure above, which is used to filter the auto configuration class
    AnnotationAttributes attributes = getAttributes(annotationMetadata);  
    // This is the auto configuration class in the loading container
    List<string> configurations = getCandidateConfigurations(annotationMetadata, attributes);  
    // To remove the repetition, put it in the set and then in the list
    configurations = removeDuplicates(configurations);  
    // Remove what should be excluded
    Set<string> exclusions = getExclusions(annotationMetadata, attributes);  
    checkExcludedClasses(configurations, exclusions);  
    configurations.removeAll(exclusions); 
  
    // Through filter filtering, the following analysis shows that there are only 22 after filtering
    configurations = filter(configurations, autoConfigurationMetadata);  
    // Release an event. It seems that there is no key to it
    fireAutoConfigurationImportEvents(configurations, exclusions);  
    // Encapsulate configurations as AutoConfigurationEntry to return
    return new AutoConfigurationEntry(configurations, exclusions);  
}

protected boolean isEnabled(AnnotationMetadata metadata) {  
    if (getClass() == AutoConfigurationImportSelector.class) {  
  // Determine whether this attribute is configured. If not, it defaults to true,
  // String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
        return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);  
    }  
    return true;  
}
protected List<string> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 
    // getSpringFactoriesLoaderFactoryClass() returns ENA bleAutoConfiguration.class , this is to get all the field configuration classes in the container
    List<string> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),  
                                                                                getBeanClassLoader());  
    return configurations;  
}
protected List<autoconfigurationimportfilter> getAutoConfigurationImportFilters() {  
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);  
}

4. Step 5 Analysis

// The above analysis is step 4 of configuration class parsing
parser.parse(candidates);

// See Step 5 below. The configuration classes parsed in step 4 will be placed in the configurationClasses
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
this.reader.loadBeanDefinitions(configClasses);
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
  loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
}
private void loadBeanDefinitionsForConfigurationClass(
  ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
     // Determine whether to skip
     if (trackedConditionEvaluator.shouldSkip(configClass)) {
  return;
     }

     // Inject the BeanDefinition represented by this configuration class into the container
     if (configClass.isImported()) {
  registerBeanDefinitionForImportedConfigurationClass(configClass);
     }
     // Traverse all Bean methods of this configuration class, inject the BeanDefinition to be introduced by these @ Bean annotated methods
     for (BeanMethod beanMethod : configClass.getBeanMethods()) {
  loadBeanDefinitionsForBeanMethod(beanMethod);
     }

     // Processing the configuration file specified by @ ImportResource on the configuration class is the common XML configuration method in Spring
     // This side will inject beandefinition into the container by parsing the XML file. This method is not recommended in spring boot, so we won't go further
     loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
   
     // Process the importBeanDefinitionRegistrars obtained when D in step 4 parses the @ Import annotation, and call its registerBeanDefinitions
     // Method to inject BeanDefinition into the container, which was already mentioned in the @ Import method
     loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
     registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry));
}

4, Summary

At this point, the configuration class parsing of SpringBoot is basically finished, briefly review:

  1. The first part introduces some classes and annotations that will be used when SpringBoot parses configuration classes, and explains their use and principle.
  2. The second part introduces the SpringBoot startup process, and leads to the step in which SpringBoot resolves configuration classes.
  3. The third part introduces the eight steps of spring boot parsing configuration class.

More content, please pay attention to vivo Internet technology WeChat official account.

Note: please contact wechat: Labs2020 for reprint.

Tags: Programming Spring SpringBoot xml Java

Posted on Thu, 18 Jun 2020 04:13:37 -0400 by ecko