Spring Boot - In-depth Principle - Automatic Configuration

Keywords: Web Development Spring Attribute

2.2.2 Automatic Configuration

The ability to automatically configure for us when we add jar package dependencies allows us to run written projects without configuring or with a small number of configurations.

Question: How on earth does Spring Boot automatically configure its components?

2.2.2.1 @SpringBootApplication

The entry where the Spring Boot application starts is the main method of the @SpringBootApplication annotation class.

@SpringBootApplication scans Spring components and automatically configures Spring Boot

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

@SpringBootApplication annotation class

// Scope of application of annotations: classes, interfaces, enumerations
@Target({ElementType.TYPE})
// Life cycle of annotations: Runtime
@Retention(RetentionPolicy.RUNTIME)
// Label annotations can be labeled in javadoc
@Documented
// Indicates that annotations can be inherited by subclasses
@Inherited
// Mark this class as a configuration class
@SpringBootConfiguration
// Start auto-configuration
@EnableAutoConfiguration
// Packet Scanner
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

As you can see above, the @SpringBootApplication annotation consists mainly of three core annotations @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan.

2.2.2.1.1 @SpringBootConfiguration

The @SpringBootConfiguration annotation states that its class is the Spring Boot configuration class

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// Configure to IOC Container
@Configuration
public @interface SpringBootConfiguration {
}

As you can see from the above, the @SpringBootConfiguration annotation class is mainly annotated with the @Configuration annotation, which is provided by the Spring framework to indicate that the current class is a configuration class and can be scanned by the component scanner.

2.2.2.1.2 @EnableAutoConfiguration

The @EnableAutoConfiguration annotation is represented as an auto-configuration class, which is the most important annotation for Spring Boot and is also the annotation that implements auto-configuration.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// Automatic Configuration Package
@AutoConfigurationPackage
// Automatically configure scan import
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

From the source code, you can see that the @EnableAutoConfiguration annotation is a combined annotation that imports beans that need to be registered with IOC for a particular scene with the @Import annotation and loads them into the IOC container.The @AutoConfiguration Package uses @Import to collect all Bean definitions that meet the automatic configuration criteria and load them into the IOC container.

  • @AutoConfigurationPackage

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    // Import components registered in Registrar
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    }

    From the above source code, you can see that the @AutoConfiguration Package annotation functions with the @Import annotation.@Import is the underlying Spring Framework comment that imports a component class to a container

    Registrar Class Source

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
              // Scan the package in which the main program class resides and the components under all subpackages into the Spring container
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }
    
    }

    As you can see from the above, the @AutoConfiguration Package annotation is designed to load the package in which the main program class resides and the components under all subpackages into the IOC container.

    Therefore, when defining a project package directory, it is required that the package structure defined be canonical, that the project main program startup class be placed in the outermost root directory location, and then subpackages and classes are built inside the root directory location for business development, so that the defined classes can be scanned by the component scanner.

  • @Import({AutoConfigurationImportSelector.class})

    Import the AutoConfigurationImportSelector class into the Spring container.

    AutoConfiguration ImportSelector can help Spring Boot applications import all eligible @Configuration configurations into the IOC container (ApplicationContext) that the current Spring Boot creates and uses.

    // The process of automatic configuration
    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()));
      // Get the auto-configured configuration class
      AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
          .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
      this.autoConfigurationEntries.add(autoConfigurationEntry);
      for (String importClassName : autoConfigurationEntry.getConfigurations()) {
          this.entries.putIfAbsent(importClassName, annotationMetadata);
      }
    }
    
    // Get Auto Configuration Meta Information
    private AutoConfigurationMetadata getAutoConfigurationMetadata() {
      if (this.autoConfigurationMetadata == null) {
          // Loading auto configuration meta information requires passing in the beanClassLoader class loader
          this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
      }
      return this.autoConfigurationMetadata;
    }
    
    // Get the auto-configured configuration class
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
          // From META-INF/Spring.factoriesGet the auto-configuration class for in the configuration file to
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

Deep into AutoConfigurationMetadataLoader.loadMetadata() Method

// Class path of configuration class to be loaded in file
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

private AutoConfigurationMetadataLoader() {
}

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
        // Read spring-boot-autoconfigure-2.1.14.RELEASE.jar Middle spring-autoconfigure-Metadata.propertiesInformation Generation URL
        Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
        Properties properties = new Properties();
        while (urls.hasMoreElements()) {
            properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
        }
        return loadMetadata(properties);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
}

Deep into AutoConfigurationImportSelector.getCandidateConfigurations() Method

This method has an important loadFactoryNames method that lets SpringFactoriesLoader load the names of some components.

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // This method requires two parameters, getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()
    // getSpringFactoriesLoaderFactoryClass() returns: EnableAutoConfiguration.class
    // Return from getBeanClassLoader(): beanClassLoader class loader
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

protected ClassLoader getBeanClassLoader() {
    return this.beanClassLoader;
}

Continue to dig deeper into the loadFactoryNames() method

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        // Acquired health
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

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

        try {
            // If the class loader is not empty, load META-INF/Spring.factoriesEncapsulates the class path information of the configuration class set in it as an Enumeration object
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources("META-INF/spring.factories") :
                    ClassLoader.getSystemResources("META-INF/spring.factories"));
            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()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

Will read oneSpring.factoriesFile, read without error

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

It actually loads an external file that is

The @EnableAutoConfiguration annotation is to search for META-INF/Spring.factoriesConfiguration file, andOrg.springframework.boot.Autoconfigure.EnableAutoConfigurationThe corresponding JavaConfig configuration class labeled @Configuration is instantiated by reflection for the configuration and loaded into the IOC container.

In the case of a web project, a web environment dependency launcher is added to the project, corresponding to theOrg.springframework.boot.Autoconfigure.web.servletThe.WebMvcAutoConfiguration auto-configuration will take effect, and when you turn on the auto-configuration, you will find that the Spring MVC is fully annotated in the configuration classThe default configuration of the environment required for the running environment includes prefix, suffix, attempt parser, MVC validator, and so on.

summary

Steps for underlying Spring Boot auto-configuration:

1) Spring Boot application startup

2)@SpringBootApplication comment works

3)@EnableAutoConfiguration comment works

4)@AutoConfiguration Package comment works

The main purpose of the @AutoConfiguration Package comment is @Import({AutoC)OnfigurationPackages.Registrar.class}), which is imported into the container through the Registrar class, which scans the packages and subpackages of the main configuration class and imports the corresponding components into the IOC container.

5)@Import(AutoConfigurationImportSelector.class)

@Import (AutoConfigu)RationImportSelector.class) It imports the AutoConfigurationImportSelector class into the container, AutoConfigurationImportSelectorThe purpose of a class is to use the internal tool class SpringFactoriesLoader to find META-INF/s in all jar packages on the classpath during execution through the getAutoConfiguration Entry() methodSpring.factoriesLoading implements a series of container creation processes that communicate configuration information to the Spring Factory loader.

2.2.2.1.3 @ComponentScan

@ComponentScan annotates the path of the specific scan package, determined by the location of the package where the Spring Boot main program is located.During the scan, the @AutoConfiguration Package annotation parses the package to get the location of the Spring Boot main program class

Posted by carsale on Mon, 15 Jun 2020 10:37:14 -0700