Automatic assembly mechanism of SpringBoot

Posted by massimoGornatti on Fri, 11 Feb 2022 12:54:11 +0100

​SpringBoot3.0 is coming out. It is said that the minimum requirement of JDK is 17??? My darling, JDK8 hasn't fully understood. First learn about springboot.

1, What is SpringBoot

Official description:

Translation:

Through Spring Boot, you can easily create independent, production level Spring based applications and "run" them. In fact, Spring Boot is designed to let you run Spring applications as fast as possible and reduce your configuration files as much as possible.

2, Features of SpringBoot

① SpringBoot Starter: it integrates the commonly used dependency groups and combines them into one dependency, so that they can be added to the Maven or Gradle construction of the project at one time.

② Make the coding simple. SpringBoot configures Spring in the way of JavaConfig, and provides a large number of annotations, which greatly improves the work efficiency.

③ Automatic configuration: the automatic configuration feature of SpringBoot makes use of Spring's support for conditional configuration to reasonably infer the bean s required by the application and automatically configure them.

④ To make deployment simple, SpringBoot has three built-in Servlet containers: Tomcat, Jetty and undertow. We only need a Java running environment to run the SpringBoot project. The SpringBoot project can be packaged into a jar package.

3, SpringBoot project construction

Learning the automatic assembly of SpringBoot must start from the main entry class of Spring. Establish a SpringBoot test test project, and import the start dependency of Spring MVC to observe how to carry out automatic assembly.

@RestControllerpublic class TestController {    @RequestMapping("/test")    public String testMethod() {        return "test SpringBoot";    }}

4, Automatic assembly mechanism of SpringBoot

To analyze the automatic assembly mechanism of SpringBoot, we must start with the main startup class of SpringBoot. The main startup class has only one run method and one @ SpringBootApplication annotation. Let's first analyze the @ SpringBootApplication annotation to facilitate the interpretation of subsequent source code.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication { 
  
  /**
  * Exclude specific classes according to class so that they cannot be added to the spring container. The value type of the passed in parameter is class type.
  */
  @AliasFor(annotation = EnableAutoConfiguration.class)
  Class<?>[] exclude() default {};
​
  /**
  * Exclude specific classes according to the classname so that they cannot be added to the spring container. The type of the passed in parameter value is the full class name string array of class.
  */
  @AliasFor(annotation = EnableAutoConfiguration.class)
  String[] excludeName() default {};
​
  /**
  * Specifies the scanning package. The parameter is a string array of package names.
  */
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  String[] scanBasePackages() default {};
  
  /**
  * Scan specific packages, and the parameters are similar to Class type arrays.
  */
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  Class<?>[] scanBasePackageClasses() default {};
}

Where @ Target, @ Retention, @ Documented, @ Inherited are the four meta annotations of user-defined annotations.

@Target: indicates the applicable scope of the annotation

@Retention: indicates the lifecycle of the annotation

@Document: indicates that annotations can be recorded in javadoc

@Inherited: indicates that the subclass can inherit the annotation

@SpringBootConfiguration: indicates that this class is a configuration class

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration  //It is actually a configuration class
@Indexed
public @interface SpringBootConfiguration {
  ...
}

@Enable autoconfiguration: start the auto assembly function

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage 
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  ...
}
​
​
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
  ...
}

In EnableAutoConfiguration, there are two annotations that use the @ import annotation to import the corresponding classes@ Import annotation is an annotation provided by Spring, which is used to import configuration classes or some classes that need to be preloaded.

@ComponentSacn: scanning path. By default, the package and its sub packages of this class are scanned
5, Execution flow of run method

After briefly introducing the @ SpringBootApplication annotation, we begin to introduce the run method of SpringBoot.

Tips: This article uses springboot 2 6.3. Small partners can adjust themselves, but the overall process changes little. You can Debug step by step while watching.

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
  return run(new Class<?>[] { primarySource }, args);
}
​
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  return new SpringApplication(primarySources).run(args);
}

We can see that there are two steps to execute the run method: the first step is to instantiate the SpringApplication object, and the second step is to execute the run method.

(1) Instantiate SpringApplication:

public SpringApplication(Class<?>... primarySources) {
  this(null, primarySources);
}
​
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  //Set resource loader to null
  this.resourceLoader = resourceLoader;
  //Assert that the resource class cannot be null , let's pass the springboottestapplication class
  Assert.notNull(primarySources, "PrimarySources must not be null");
  //Encapsulated in a linkedHashSet
  this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  //It is inferred that the current application type} is generally servlet
  this.webApplicationType = WebApplicationType.deduceFromClasspath();
  //Initialize meta-inf / spring. Under classpath Configured in factories
  //key is the class of bootstrap registryinitializer
  this.bootstrapRegistryInitializers = new ArrayList<>(
      getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
  //Initialize meta-inf / spring. Under classpath Configured in factories
  //key is the class of ApplicationContextInitializer
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  //Initialize meta-inf / spring. Under classpath Configured in factories
  //key is the class of ApplicationListener
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  //Infer the class name of the main method according to the call stack
  this.mainApplicationClass = deduceMainApplicationClass();
}

Among the construction methods of SpringApplication, one of the most important methods is: getspringfactoryesinstances, which will eventually call loadSpringFactories to get all keys and values encapsulated in a map < string, list < string > > container, obtain all values through the desired keys, and then create Spring Factory instances to sort and return.

Take a look at spring. Com under META-INF The factories file is a key value pair composed of key and value.

We can Debug to see the execution results of loadspring factories

(2) Execute run method

/**
 * Run the spring application and refresh a new ApplicationContext (spring context)
 * ConfigurableApplicationContext Is a sub interface of the ApplicationContext interface.
 * A tool for configuring context is added on the basis of ApplicationContext.
 */
public ConfigurableApplicationContext run(String... args) {
  //Record program running time
  long startTime = System.nanoTime();
  //Create a default context
  DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  //Create context for spring
  ConfigurableApplicationContext context = null;
  configureHeadlessProperty();
  //From meta-inf / spring Get listeners from factories
  //The key is springapplicationrunlistener class
  SpringApplicationRunListeners listeners = getRunListeners(args);
  //lsnrctl start 
  listeners.starting(bootstrapContext, this.mainApplicationClass);
  try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    //Construct application context
    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    //Processing bean s that need to be ignored
    configureIgnoreBeanInfo(environment);
    //Print banner is the startup icon of SpringBoot, which can be customized
    Banner printedBanner = printBanner(environment);
    //Initialize application context
    context = createApplicationContext();
    context.setApplicationStartup(this.applicationStartup);
    //Preparation phase before refreshing application context
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    //Refresh application context
    refreshContext(context);
    //The embodiment of the extension interface} template mode after refreshing the application context. Here is an empty implementation
    afterRefresh(context, applicationArguments);
    Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
    }
    //Publish container startup completion event
    listeners.started(context, timeTakenToStartup);
    callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, listeners);
    throw new IllegalStateException(ex);
  }
  try {
    Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
    listeners.ready(context, timeTakenToReady);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, null);
    throw new IllegalStateException(ex);
  }
  return context;
}

In the run method, it is roughly divided into six steps: obtaining and starting the listener, constructing the application context environment, initializing the application context environment, preparing before refreshing the application context text, refreshing the application context, and the extension method after refreshing the application context. Let's analyze them respectively:

① Get and start the listener getRunListeners.

Event mechanism is a very important part of Spring. Through the event mechanism, we can listen to some events happening in the Spring container, and we can also customize the listening events.

private SpringApplicationRunListeners getRunListeners(String[] args) {
  Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  return new SpringApplicationRunListeners(logger,
      getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
      this.applicationStartup);
}

After observation, it is found that the getspringfactoryesinstances method is called to perform spring. For META-INF Read the factories file.

② Build the application context prepareEnvironment.

Including computer environment, Java environment, Spring running environment and our own application YML (application. Properties) configuration file

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
  // Create and configure the environment
  //Create and configure the appropriate environment
  ConfigurableEnvironment environment = getOrCreateEnvironment();
  //According to the user configuration, configuring the environment system environment is to encapsulate the args configureEnvironment(environment, applicationArguments.getSourceArgs());
  ConfigurationPropertySources.attach(environment);
  //Start the corresponding listener, the most important of which is
  //EnvironmentPostProcessorApplicationListener loads the configuration file
  listeners.environmentPrepared(bootstrapContext, environment);
  DefaultPropertiesPropertySource.moveToEnd(environment);
  Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
      "Environment prefix cannot be set via properties.");
  bindToSpringApplication(environment);
  if (!this.isCustomEnvironment) {
    environment = convertEnvironment(environment);
  }
  ConfigurationPropertySources.attach(environment);
  return environment;
}

Let's Debug to see the effect.

Listeners. Is not executed environmentPrepared

After execution, the properties in our configuration file are added.

When you start the listener,

SpringBoot2. ConfigFileApplicationListener is discarded after 4.0, and 3.0 will be removed.

/**
 * @deprecated since 2.4.0 for removal in 3.0.0 in favor of
 * {@link ConfigDataEnvironmentPostProcessor}
 */
@Deprecated
public class ConfigFileApplicationListener

Then we used the environment postprocessor applicationlistener to load our configuration file. We can go deep into the main line here.

③ Initialize the application context createApplicationContext.

In the SpringBoot project, there are three types of applications, as shown in the following code.

public enum WebApplicationType {
​
  /**
   * The application is not a web application and should not be started with a web server
   * The application should not run as a web application and should not start an
   * embedded web server.
   */
  NONE,
​
  /**
   * The application should run as a servlet based web application and should start the embedded servlet web (tomcat) server.
   * The application should run as a servlet-based web application and should start an
   * embedded servlet web server.
   */
  SERVLET,
​
  /**
   * The application shall run as a reactive web application and the embedded reactive web server shall be started.
   * The application should run as a reactive web application and should start an
   * embedded reactive web server.
   */
  REACTIVE;
}

Corresponding to the three application types, SpringBoot project has three corresponding application contexts. We take web engineering as an example, that is, its application context is:

AnnotationConfigServletWebServerApplicationContext.

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
  try {
    switch (webApplicationType) {
    case SERVLET:
      return new AnnotationConfigServletWebServerApplicationContext();
    case REACTIVE:
      return new AnnotationConfigReactiveWebServerApplicationContext();
    default:
      return new AnnotationConfigApplicationContext();
    }
  }
  catch (Exception ex) {
    throw new IllegalStateException("Unable create a default ApplicationContext instance, "
        + "you may need a custom ApplicationContextFactory", ex);
  }
};

Application context can be understood as the high-level expression of IoC container. Application context really enriches some high-level functions on the basis of IoC container. The application context holds a relationship with the IoC container. The attribute beanFactory is the IoC container DefaultListableBeanFactory. So there is a holding and expanding relationship between them.

Class annotationconfigservletwebserver ApplicationContext inherits the class GenericApplicationContext. beanFactory (DefaultListableBeanFactory) is instantiated in the construction method of GenericApplicationContext. Therefore, the IoC container is also created here. We observe the execution of the createApplicationContext() method in Debug.

Before execution:

After execution:

④ Preparations before refreshing the application context prepareContext()

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
    ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments, Banner printedBanner) {
  //Set context
  context.setEnvironment(environment);
  //Execute post processor
  postProcessApplicationContext(context);
  //ApplicationContextInitializer in the execution context
  applyInitializers(context);
  //Send events with prepared context to each listener
  listeners.contextPrepared(context);
  bootstrapContext.close(context);
  if (this.logStartupInfo) {
    logStartupInfo(context.getParent() == null);
    logStartupProfileInfo(context);
  }
  // Add boot specific singleton beans
  //Encapsulate the args parameter in the main function into a singleton Bean and register it into the container
  ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
  //The printedbinder is also encapsulated into a singleton and registered into the container
  if (printedBanner != null) {
    beanFactory.registerSingleton("springBootBanner", printedBanner);
  }
  if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
    //Sets whether circular references are allowed
    ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
    if (beanFactory instanceof DefaultListableBeanFactory) {
      //Set whether to allow overriding bean s
      ((DefaultListableBeanFactory) beanFactory)
          .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
  }
  //Lazy loading
  if (this.lazyInitialization) {
    context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
  }
  // Load the sources
  Set<Object> sources = getAllSources();
  Assert.notEmpty(sources, "Sources must not be empty");
  //Load the startup class and inject the startup class into the container
  load(context, sources.toArray(new Object[0]));
  //Publish container loaded events
  listeners.contextLoaded(context);
}

getAllSources is to get our main startup class

load(context, sources.toArray(new Object[0]) injects our startup class into the container. After the load method encapsulates our object as a BeanDefinition, we finally call the registerBeanDefinition() method to register the object in beanDefinitionMap.

protected void load(ApplicationContext context, Object[] sources) {
  if (logger.isDebugEnabled()) {
    logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
  }
  BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
  if (this.beanNameGenerator != null) {
    loader.setBeanNameGenerator(this.beanNameGenerator);
  }
  if (this.resourceLoader != null) {
    loader.setResourceLoader(this.resourceLoader);
  }
  if (this.environment != null) {
    loader.setEnvironment(this.environment);
  }
  loader.load();
}

Before load ing:

After load ing:

⑤ Refresh the application context refreshContext(), that is, initialize the IoC container.

Initializing the IoC container is roughly divided into three steps: locating the beandefinited Resource, loading the BeanDefinition, and registering the BeanDefinition into the IoC container.

When the refreshContext is executed, Spring's refresh method is executed. At this time, Spring boot encapsulates Spring and finally executes the refresh method.

private void refreshContext(ConfigurableApplicationContext context) {
  if (this.registerShutdownHook) {
    shutdownHook.registerApplicationContext(context);
  }
  refresh(context);
}
​
protected void refresh(ConfigurableApplicationContext applicationContext) {
  applicationContext.refresh();
}
​
public void refresh() throws BeansException, IllegalStateException {
  synchronized (this.startupShutdownMonitor) {
    StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
    //Refresh context
    prepareRefresh();
    
    //This is where refreshBeanFactory() is started in the subclass
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    //Prepare the bean factory for use in this context
    prepareBeanFactory(beanFactory);
    try {
      //Set the post processor of beanFactory
      postProcessBeanFactory(beanFactory);
      StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
      //Call the post processors of BeanFactory, which are registered with the container in the Bean definition
      invokeBeanFactoryPostProcessors(beanFactory);
      
      //Register the postprocessing of Bean, and call it in the process of Bean creation.
      registerBeanPostProcessors(beanFactory);
      beanPostProcess.end();
      
      //Initializes the message source in the context
      initMessageSource();
      
      //Initialize event mechanism in context
      initApplicationEventMulticaster();
​
      //Initialize other special beans
      onRefresh();
​
      //Check the listening beans and register them with the container
      registerListeners();
​
      //Instantiate all (non lazy init) singletons
      finishBeanFactoryInitialization(beanFactory);
​
      //Publish the container event and end the Refresh process
      finishRefresh();
    }
​
    catch (BeansException ex) {
      if (logger.isWarnEnabled()) {
        logger.warn("Exception encountered during context initialization - " +
            "cancelling refresh attempt: " + ex);
      }
​
      // Destroy already created singletons to avoid dangling resources.
      destroyBeans();
​
      // Reset 'active' flag.
      cancelRefresh(ex);
​
      // Propagate exception to caller.
      throw ex;
    }
​
    finally {
      // Reset common introspection caches in Spring's core, since we
      // might not ever need metadata for singleton beans anymore...
      resetCommonCaches();
      contextRefresh.end();
    }
  }
}

Since the above code is the source code of Spring, it will not be interpreted this time. If you are interested, you can continue to study the details in depth. I will directly jump to the method invokebeanfactoryprocessors related to SpringBoot automatic assembly for interpretation.

In the invokebeanfactoryprocessors method, the initialization of the IoC container is completed. There are three steps:

1> Resource location

In SpringBoot, we know that package scanning starts from the main class. In the prepareContext method, the main class has been encapsulated as BeanDefinition, and then in this method, the BeanDefinition of the main class is parsed to obtain the path of basePackage, and the resource location is completed. Secondly, SpringBoot's starter realizes automatic assembly through SPI mechanism, and the classes specified by @ Import annotation are also loaded.

2> Loading of beandefinition

The so-called loading is to use the basePackage located above. SpringBoot splices the path into: classpath: COM / study / * * / Class, and then load it to determine whether there is @ Component annotation. If so, load BeanDefinition.

3> Register BeanDefinition

The registration process is to register the BeanDefinition parsed in the loading process with the IoC container. All the way, the trace will enter the parse method of ConfigurationClassParser (because the Spring source code is too long to be the focus of this time, the call stack will be given directly)

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  for (BeanDefinitionHolder holder : configCandidates) {
    BeanDefinition bd = holder.getBeanDefinition();
    try {
      if (bd instanceof AnnotatedBeanDefinition) {
        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
      }
      else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
        parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
      }
      else {
        parse(bd.getBeanClassName(), holder.getBeanName());
      }
    }
    catch (BeanDefinitionStoreException ex) {
      throw ex;
    }
    catch (Throwable ex) {
      throw new BeanDefinitionStoreException(
          "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
    }
  }
  //Load configuration (for SpringBoot project, this is the entrance of SpringBoot auto assembly)
  this.deferredImportSelectorHandler.process();
}

Then enter the parse method

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
  processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
​
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
  if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    return;
  }
​
  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.
  SourceClass sourceClass = asSourceClass(configClass, filter);
  do {
    sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
  }
  while (sourceClass != null);
​
  this.configurationClasses.put(configClass, configClass);
}

Seeing the method starting with do, experienced partners already know that this method is a real working method (doProcessConfigurationClass).

//This method is very long. Small partners can open the source code and browse. Only fragments are intercepted here
protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    ...
    // Process any @ComponentScan annotations
    //Scan the beans in the project according to the @ ComponentScan annotation (this annotation is available on the SpringBoot startup class)
    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
        //Execute the scan immediately. (why does the SpringBoot project scan from the package where the main class is located? That's the key.)
        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());
          }
        }
      }
    }
    //Recursively handle @ Import annotation
    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    ...
}

In the doProcessConfigurationClass method, the @ Component annotation and @ Import annotation of the main class will be read and the logic of the response will be executed. Let's keep looking down

this.componentScanParser.parse method

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
  ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
      componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
  ...
  if (basePackages.isEmpty()) {
    basePackages.add(ClassUtils.getPackageName(declaringClass));
  }    
  ...
  return scanner.doScan(StringUtils.toStringArray(basePackages));
}

After entering the doScan method, inject the class with @ Component into the container.

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  for (String basePackage : basePackages) {
    //Scan the Bean to be loaded from the specified package
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    for (BeanDefinition candidate : candidates) {
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
      candidate.setScope(scopeMetadata.getScopeName());
      String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
      if (candidate instanceof AbstractBeanDefinition) {
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      }
      if (candidate instanceof AnnotatedBeanDefinition) {
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      }
      if (checkCandidate(beanName, candidate)) {
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
        definitionHolder =
            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        beanDefinitions.add(definitionHolder);
        //Register the Bean into the Bean IoC container (beanDefinitionMap)
        registerBeanDefinition(definitionHolder, this.registry);
      }
    }
  }
  return beanDefinitions;
}

We see that after parsing com After the study package, testController is encapsulated and loaded into.

Then there is the important @ Import parsing

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
  Set<SourceClass> imports = new LinkedHashSet<>();
  Set<SourceClass> visited = new LinkedHashSet<>();
  collectImports(sourceClass, imports, visited);
  return imports;
}
​
//Recursively Find @ Import annotation
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
    throws IOException {
​
  if (visited.add(sourceClass)) {
    for (SourceClass annotation : sourceClass.getAnnotations()) {
      String annName = annotation.getMetadata().getClassName();
      if (!annName.equals(Import.class.getName())) {
        collectImports(annotation, imports, visited);
      }
    }
    imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
  }
}

Here @ import is only parsed and not processed.

We return to the parse method of ConfigurationClassParser, which will be executed at the end of the method

// To execute component classes 
this.deferredImportSelectorHandler.process();

After entering the process, go all the way to the method processGroupImports()

public void processGroupImports() {
  for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    Predicate<String> exclusionFilter = grouping.getCandidateFilter();
    grouping.getImports().forEach(entry -> {
      ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
      try {
        processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
            Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
            exclusionFilter, false);
      }
      catch (BeanDefinitionStoreException ex) {
        throw ex;
      }
      catch (Throwable ex) {
        throw new BeanDefinitionStoreException(
            "Failed to process import candidates for configuration class [" +
                configurationClass.getMetadata().getClassName() + "]", ex);
      }
    });
  }
}

getImports() is called here.

public Iterable<Group.Entry> getImports() {
  //Traverse the DeferredImportSelectorHolder object collection deferredImports,
  //The deferredImports collection contains various importselectors,
  //Of course, the AutoConfigurationImportSelector is installed here
  for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    //Use the process method of AutoConfigurationGroup to process automatic configuration
    //To determine which configuration classes to import
    this.group.process(deferredImport.getConfigurationClass().getMetadata(),
        deferredImport.getImportSelector());
  }
  //After the above processing, select which configuration classes to import
  return this.group.selectImports();
}

The process and selectImports methods in the internal class AutoConfigurationGroup in the class AutoConfigurationImportSelector are then called.

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
  Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
      () -> String.format("Only %s implementations are supported, got %s",
          AutoConfigurationImportSelector.class.getSimpleName(),
          deferredImportSelector.getClass().getName()));
  //Call the getAutoConfigurationEntry method to get the autoconfiguration class and put it into the autoConfigurationEntry object
  AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
      .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
  //The autoConfigurationEntry object that encapsulates the automatic configuration class is loaded into the autoConfigurationEntries collection
  this.autoConfigurationEntries.add(autoConfigurationEntry);
  for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    //Here, the qualified automatic configuration class is put into the entries collection as the key and the annotationMetadata as the value
    this.entries.putIfAbsent(importClassName, annotationMetadata);
  }
}
​
@Override
public Iterable<Entry> selectImports() {
  if (this.autoConfigurationEntries.isEmpty()) {
    return Collections.emptyList();
  }
  Set<String> allExclusions = this.autoConfigurationEntries.stream()
      .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
  Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
      .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
      .collect(Collectors.toCollection(LinkedHashSet::new));
  processedConfigurations.removeAll(allExclusions);
​
  return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
      .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
      .collect(Collectors.toList());
}

Enter getAutoConfigurationEntry method

protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    //Get whether spring is configured boot. The enableautoconfiguration property returns true by default
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    //Get the Configuration class marked by @ Configuration
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //Get spring All auto configuration classes configured by the factories file
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //Remove duplicate configuration classes using LinkedHashSet
    configurations = removeDuplicates(configurations);
    //Get the automatic configuration class to be excluded, such as the configuration class of annotation attribute exclude
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    //Remove the configuration class to exclude
    configurations.removeAll(exclusions);
    //Because from spring There are too many automatic configuration classes obtained from the factories file,
    //If some unnecessary automatic configuration classes are loaded into memory, memory will be wasted,
    //Therefore, filtering is required here, and AutoConfigurationImportFilter will be called here
    //To determine whether it conforms to @ ConditionalOnBean,
    //@ConditionalOnClass or @ ConditionalOnWebApplication
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    //Encapsulate the eligible and excluded autoconfigurationclasses into the AutoConfigurationEntry object
    return new AutoConfigurationEntry(configurations, exclusions);
}

In the getCandidateConfigurations method, the loadFactoryNames method will be called to load the spring.inf under META-INF Factories file

The key this time is EnableAutoConfiguration.

AutoConfigurationImportSelector can help springboot applications load all qualified @ Configuration configurations into the IOC container (ApplicationContext) created and used by the current springboot.

In spring There are many automatic assembly classes in factories, but not all of them will be loaded. Only those that meet the conditions will be loaded into the IoC container.

Before filtration:

After filtration:

Because in spring There are many configuration classes in the factories file that are not what we need. For example, RabbitAutoConfiguration in the figure below will not be assembled if we do not use it. If we import spring MVC, DispatcherServlet and HttpEncoding will be assembled. Only when the conditions on the automatic assembly class are met can they be assembled.

Let's take a look at the effect of executing the refreshContext method.

Before execution:

After execution:

At this point, all bean s that comply with the automatic assembly will be automatically loaded into the IoC container of Spring.

⑥ Extension method after refreshing the application context

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

Extension method, the template method in design pattern. It is empty by default. If you have custom requirements, you can override this method.

So far, the automatic assembly of SpringBoot has been completed.

6, Summary

The principle of SpringBoot automatic configuration mainly does the following things:

① From spring Load automatic configuration class in factories configuration file

② The auto configuration class specified by the exclude property of the @ EnableAutoConfiguration annotation is excluded from the loaded auto configuration class

③ Then use the AutoConfigurationImportFilter interface to filter whether the autoconfiguration class meets the conditions of its annotation @ conditionalonclass, @ conditionalonbean and @ ConditionalOnWebApplication. If they all meet the conditions, the matching result will be returned.

④ The AutoConfigurationImportEvent event event is then triggered

Tell ConditionEvaluationReport the condition evaluation reporter object to record the auto configuration classes that meet the conditions and exclude respectively.

⑤ Finally, spring imports the auto configuration class after the final filtering into the IOC container

That's the end of today's sharing. Remember to praise your little friends.

Pay attention to the official account number JavaGrowUp, reply to interview, get interview questions, prepare for gold four silver, and we will see you next time.

Topics: Java Spring Spring Boot