Read the harvest

👍🏻 Understand SpringBoot automatic configuration

What is SpringBoot

The birth of SpringBoot is to simplify the tedious XML configuration in Spring, its essence is still the Spring framework, using SpringBoot can not use any XML configuration to start a service, so that we can use microservice architecture can be more quickly to build an application.

The short version is that SpringBoot is not a new framework, and many frameworks are configured by default.

Two, SpringBoot characteristics

  • Fixed configurations are provided to simplify configuration, i.eConvention greater than Configuration
  • Automatically configure Spring and third-party libraries as much as possibleAutomatically.
  • Embedded containers to create standalone Spring applications
  • Let the Test become simple, built-in JUnit, Spring Boot Test and other testing frameworks, convenient testing
  • Provides production-ready features such as metrics, health checks, and externalized configurations.
  • No code generation is required and no XML configuration is required.

Three, the startup class

Below explore the startup principle of SpringBoot, about some details do not repeat, we catch the main line can be analyzed.

Note: The SpringBoot version of this article is 2.6.1

3.1 @ SpringBootApplication

Everything comes from the boot class that originated SpringBoot, and we find that the main method has an annotation: @SpringBootApplication

@SpringBootApplication
public class SpringbootWorkApplication {
    public static void main(String[] args) { SpringApplication.run(SpringbootWorkApplication.class, args); }}Copy the code

The @SpringBootApplication annotation indicates on a class that this class is the main configuration class for SpringBoot, and SpringBoot should run the class’s main method to start the SpringBoot application; It is essentially a composite annotation, which we click on to view the meta information of this class. It contains three annotations:

@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 {
Copy the code
  • @SpringBootConfiguration(Inside is @Configuration, which marks the current class as Configuration, but it’s just a layer of encapsulation and a name change.)
  • @EnableAutoConfiguration(Enable automatic configuration)
  • @ComponentScan(Packet scanning)

Note: @Inherited is an identifier used to decorate annotations. If a class uses the @Inherited annotation, its child classes will inherit this annotation

Let’s examine each of these three annotation functions

3.1.1 @ SpringBootConfiguration

Go ahead and click @springBootConfiguration to view the source code as follows:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor( annotation = Configuration.class )
    boolean proxyBeanMethods(a) default true;
}
Copy the code

The @Configuration annotation on a class indicates that it is a SpringBoot Configuration class. You can inject components into containers.

3.1.2 @ ComponentScan

  • @ComponentScan: Configures the component scanning instruction for the Configuration class.
  • To provide withSpring XML<context:component-scan>Element parallelism support.
  • canbasePackageClassesbasePackages To define specific packets to scan. If no specific package is defined, the annotation will be retrieved from the class that declared the annotationPacket scan started.

3.1.3 @ EnableAutoConfiguration

  • @enableAutoConfiguration means:Enable automatic configuration import
  • This annotation is the focus of SpringBoot and will be explained in detail below

Fourth, @ EnableAutoConfiguration

  • So let’s click on that and see what that annotation says, okay
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage   // Automatic packet guide
@Import({AutoConfigurationImportSelector.class}) // Automatic configuration import selection
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<? >[] exclude()default {};

    String[] excludeName() default {};
}
Copy the code

4.1 @ AutoConfigurationPackage

  • Automatically import the configuration package
  • Click in to see the code:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default{}; Class<? >[] basePackageClasses()default {};
}
Copy the code

@import is a spring annotation, imports a configuration file, and in SpringBoot imports a component for the container, And import components made from AutoConfigurationPackages. The class of the inner class. The Registrar class execution logic to decide how to import.

4.4.1 @ Import ({Registrar. Class})

Registrar. Class to view the information via the Registrar.

static class Registrar implements ImportBeanDefinitionRegistrar.DeterminableImports {
    Registrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        / / the breakpoint
        AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    }

    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(newAutoConfigurationPackages.PackageImports(metadata)); }}Copy the code

Note: the Registrar has realized ImportBeanDefinitionRegistrar classes, can be annotated @ Import imported into the spring container.

This is the point of interruption

Running can view to (String []) (new AutoConfigurationPackages. PackageImports (metadata). GetPackageNames (.) toArray (new String[0]) has the value com.ljw.springBootWork: the package name of the currently started class

Conclusion: the @AutoConfigurationPackage is a way to scan and register all components under the package that contains the main configuration class (annotated by @SpringBootApplication) into the Spring container.

4.2 @ Import ({AutoConfigurationImportSelector. Class})

Function: AutoConfigurationImportSelector open automatic configuration class selector guide package, is into which classes, selective import

Point AutoConfigurationImportSelector class into view the source code, this class has two methods of knowledge meaning,

  1. SelectImports: Select the components to be imported
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }}Copy the code
  1. GetAutoConfigurationEntry: According to the import of the @ the Configuration class AnnotationMetadata returns AutoConfigurationImportSelector. AutoConfigurationEntry
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
         // Make a breakpoint to see the data returned
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        // Delete duplicates
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        / / check
        this.checkExcludedClasses(configurations, exclusions);
        // Remove dependencies that need to be excluded
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return newAutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); }}Copy the code

Enclosing getCandidateConfigurations (annotationMetadata, attributes) breakpoints view hereConfigurations array is 133 in length and the file name extension is**AutoConfiguration

Conclusion: These are candidate configuration classes, and the final component is all that the environment needs after de-duplication, removing the required excluded dependencies. With autoconfiguration, we don’t need to manually configure the values, configuration classes have default values.

Let’s move on to see how the components that need to be configured are returned

2 getCandidateConfigurations (annotationMetadata, attributes)

The method is as follows:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}
Copy the code

Here’s an assertion: Assert.notEmpty(configurations, “No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.”);

No auto-configuration class was found in meta-INF/Spring.Factories. If you use custom wrappers, make sure the file is correct. “

Conclusion: The loadFactoryNames () method must find an automatic configuration class to return without an error.

4.2.1.1 getSpringFactoriesLoaderFactoryClass ()

We found: point in this. GetSpringFactoriesLoaderFactoryClass () returns the EnableAutoConfiguration. Class this annotation. This annotation is the same as the identity annotation under @SpringBootApplication.

protectedClass<? > getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}
Copy the code

Conclusion: Get a class that can load automatic configuration class, i.e. SpringBoot default automatic configuration class EnableAutoConfiguration

4.2.2 SpringFactoriesLoader

The SpringFactoriesLoader factory load mechanism is a convention provided by Spring. It only needs to be loaded in the meta-INF/Spring.Factories file of the module. The key in the Properties file is the full name of the interface, annotation, or abstract class, and the value is a comma “, “separated implementation class. The corresponding implementation class is injected into the Spirng container using the SpringFactoriesLoader.

Note: The meta-INF/Spring. factories files in the classpath path of all jar packages are loaded.

4.2.2.1 loadFactoryNames ()

public static List<String> loadFactoryNames(Class<? > factoryType,@Nullable ClassLoader classLoader) {
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
Copy the code

Breakpoint To view factoryTypeName:

First will EnableAutoConfiguration. Class passed factoryType

Then String factoryTypeName = factoryType.getName(); So factoryTypeName value of org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration

4.2.2.2 loadSpringFactories ()

Then look at the loadSpringFactories method in action

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // Breakpoint view
   Map<String, List<String>> result = cache.get(classLoader);
   if(result ! =null) {
      return result;
   }

   result = new HashMap<>();
   try {
      // Note here: meta-inf /spring.factories
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for(Map.Entry<? ,? > entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {
            / / the breakpoint
               result.computeIfAbsent(factoryTypeName, key -> newArrayList<>()) .add(factoryImplementationName.trim()); }}}// Replace all lists with unmodifiable lists containing unique elements
      // check the result value
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}
Copy the code

The FACTORIES_RESOURCE_LOCATION here is defined above: meta-INF /spring.factories

public final class SpringFactoriesLoader {

   /** * The location to look for factories. * 

Can be present in multiple JAR files. */

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; Copy the code

Where is the meta-INF /spring.factories file?? The meta-INF/Spring. factories files under the current classpath of all imported Java packages are read, as follows:

Breakpoint query result value as follows:

The META-INF/spring.factories () method loads the META-INF/spring.factories () file with a map containing the key () and the values ().

LoadSpringFactories (classLoaderToUse).getorDefault (factoryTypeName, collections.emptyList ());

For loadFactoryNames method carry to come over to the first parameter is EnableAutoConfiguration. The class, so factoryType value for EnableAutoConfiguration. Class, Then factoryTypeName values EnableAutoConfiguration. Is to get the value of the meta-inf/spring. Factories files under key is org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration value

GetOrDefault If the Map has a key, the key is used; if not, the empty array is used

Conclusion:

  • The loadSpringFactories() method simply loads the fully qualified class names of the factory implementation of the given type from “meta-INF/spring.Factories” into the map
  • LoadFactoryNames () is based on SpringBoot start life process, when need to load automatic configuration class, will be introduced to org. Springframework. Boot. The autoconfigure. EnableAutoConfiguration parameters, From the map to find the key for org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration values, these values through the reflection to the container, then the role of automatic configuration is to use them to do, This is where Springboot automatic configuration begins
  • Only after these auto-configuration classes are in the container does the next auto-configuration class start
  • When additional configuration is required, such as listening for related configuration: listEnter, pass a different parameter to get the related listEnter configuration.

5. Process summary diagram

Common Conditional notes

  • Instead of loading the spring.Factories configuration in its entirety, it is loaded dynamically with annotations such as @Conditional

  • @Conditional is actually a spring underlying annotation, which means to judge different conditions according to different conditions. If the specified conditions are met, the configuration in the configuration class will take effect.

  • Common Conditional notes:

    • ConditionalOnClass: this class is valid if it is present in classpath
    • @ ConditionalOnMissingClass: classpath does not exist when the class work
    • ConditionalOnBean: effect when this type of Bean is present in the DI container
    • ConditionalOnMissingBean: this type of Bean does not exist in the DI container
    • @ ConditionalOnSingleCandidate: DI container in this type of Bean has only one or only one of the @ Primary when working
    • ConditionalOnExpression: the result of the SpEL expression is true
    • ConditionalOnProperty: effect when parameter Settings or values are the same
    • ConditionalOnResource: effective when the specified file exists
    • ConditionalOnJndi: effect when the specified JNDI exists
    • ConditionalOnJava: works when the specified Java version exists
    • @ ConditionalOnWebApplication: Web application environment
    • @ ConditionalOnNotWebApplication: a Web application environment

7. @Import Supports three Import modes

  1. Configuration class with @Configuration
  2. The realization of the ImportSelector
  3. The realization of the ImportBeanDefinitionRegistrar
  • 👍🏻 : have harvest, praise encouragement!
  • ❤️ : Collect articles, easy to look back!
  • 💬 : Comment exchange, mutual progress!