How does SpringBoot automatically assemble components according to configuration?

Before analyzing the source code, let’s take a look at the theory of autoloader. Without the support of theory, we can hardly understand the essence of source code.

Start relying on

Spring Boot helps manage dependencies for projects by starting dependencies. Starting dependencies are simply special Maven dependencies and Gradle dependencies that take advantage of transitive dependency resolution to aggregate common libraries into several dependencies tailored for specific functionality.

Many starter dependencies are configured in spring-boot-starter-parent:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.1. RELEASE</version>
    <relativePath/> <! -- lookup parent from repository -->
</parent>
Copy the code

Spring Boot reduces the complexity of project dependencies by providing numerous start-up dependencies.

Automatically.

Automatic configuration of Spring Boot is an application startup process that takes many factors into account to determine which Spring configuration should and should not be used.

These are all things that Spring Boot’s automatic configuration takes into account.

  • Spring’s JdbcTemplateIs it in the Classpath? If it is, and there isDataSourceIs automatically configuredJdbcTemplateThe Bean.
  • Spring SecurityIs it in the Classpath? If so, perform a very basic Web security setup.

Spring Boot’s automatic configuration makes nearly 200 such decisions every time an application starts. All this auto-configuration is designed to keep us from writing our own configuration.

JdbcTemplate and Spring Security auto-assembly

JdbcTemplate

Spring of the Boot DataSourceAutoConfiguration JdbcTemplate defined in the Bean is a very simple example, illustrates the @ ConditionalOnMissingBean how it works:

@Bean
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate(a) {
	return new JdbcTemplate(this.dataSource);
}
Copy the code

The @Bean annotation is added to the jdbcTemplate() method so that a JdbcTemplateBean can be configured if needed.

However, it also adds the @conditionAlonmissingBean annotation, which requires that no JdbcOperations Bean currently exists.

If there is already a JdbcOperations Bean, the condition is not met and the jdbcTemplate() method is not executed.

Spring Security

Spring Boot automatically configure security configuration, one of the most important class is SpringBootWebSecurityConfiguration.

Here is a snippet of the code:

@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {

   @Configuration
   @Order(SecurityProperties.BASIC_AUTH_ORDER)
   static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {}}Copy the code

Let’s first analyze these annotations.

  • @ConditionalOnClassThe Bean will only be built if security-related packages are introduced into the project.
  • @ConditionalOnMissingBeanNote When there is no containerWebSecurityConfigurerAdapterFor instance, the default configuration is used.
  • @ConditionalOnWebApplicationNote This must be a Web application and of typeservlet

Custom Configuration

Although Spring Boot provides some basic automatic configuration, many times you need to override some of the configuration yourself to meet your needs.

Security, for example, custom configuration, we need to inherit abstract class WebSecurityConfigurerAdapter, and can be injected into the container

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {}Copy the code

Let’s test to see if it’s already injected into the container

The test class:

public class MainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = newAnnotationConfigApplicationContext(SecurityConfig.class); System.out.println(applicationContext.getBean(WebSecurityConfigurerAdapter.class)); }}Copy the code

It turns out to have been injected into the container:

version

Springboot version: 2.3.1.release

Spring version: 5.2.7.release

Source code analysis

Through the description of the above theory, then we analyze Springboot related source code, see how to achieve automatic assembly Springboot?

An overview of the

Let’s start with a picture:

Comments:

  • @SpringBootApplicationThis is the core annotation of SpringBoot, and of course the composite annotation
  • @EnableAutoConfigurationIt is the master switch for automatic assembly. We’ll start with this annotation to enhance our understanding of automatic configuration.
  • @Import(AutoConfigurationImportSelector.class)Import automatic configurationImportSelectotClass.

Core classes and methods:

  • AutoConfigurationImportSelectorImport classes or beans that need to be auto-assembled.
  • getCandidateConfigurationsGets the configuration classes for all components.
  • AutoConfigurationMetadataLoader.loadMetadataConditions for loading all autowiring component configuration classes (@ConditionalFilter conditions).
  • filter.match(candidates, this.autoConfigurationMetadata)Configure all classes based on each component@ConditionalConditional filtering is performed.

Configuration file:

Meta-inf /spring.factories stores the full pathnames of all the configuration classes for the component.

Meta-inf /spring-autoconfigure-metadata.properties stores all the filtering conditions when the component loads the configuration class.

Source details

SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration // Turn on automatic assembly switch
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
Copy the code
EnableAutoConfiguration

This note acts as a switch on automatic assembly.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
// Import selector, identify the AutoConfigutaion class in each component and load it into the container
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	// Automatic assembly switch, default true, can be set in application.properties
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   // No loaded beans are requiredClass<? >[] exclude()default {};

   // No loaded beans are required
   String[] excludeName() default {};

}
Copy the code
Focus onAutoConfigurationImportSelector

This class implements the ImportSelector interface and, most importantly, the selectImports method, which injects beans that need to be injected into the container based on the configuration file (spring.Factories).

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   // Check whether the automatic assembly switch is on
   if(! isEnabled(annotationMetadata)) {return NO_IMPORTS;
   }
   // Get all beans that need to be assembled
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
Copy the code

First of all, let’s look at how to judge the automatic assembly switch:

protected boolean isEnabled(AnnotationMetadata metadata) {
   // Determine the class of the current instance
   if (getClass() == AutoConfigurationImportSelector.class) {
      / / return spring. The boot. Enableautoconfiguration values, if it is null, return true
      / / spring. The boot. Enableautoconfiguration can be configured in the configuration file, not the null
      return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
   }
   return true;
}
Copy the code

Next, let’s look at how to get the bean we need to assemble:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   // Check the automatic assembly switch
   if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
   }
   Exclude ()/excludeName()
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // Get all the configuration classes that need to be auto-assembled and read meta-INF /spring.factories
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // go to Set and then go to List
   configurations = removeDuplicates(configurations);
   // Get excludeName from the exclude/excludeName attribute of EnableAutoConfiguration
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   Configurations Check if the classes you want to exclude are in configurations and do not report an error
   checkExcludedClasses(configurations, exclusions);
   // Remove exclusions from configurations
   configurations.removeAll(exclusions);
   Configurations are filtered out if the @conditional condition is not true
   configurations = getConfigurationClassFilter().filter(configurations);
   / / the AutoConfigurationImportEvent binding on all AutoConfigurationImportListener subclass instance
   fireAutoConfigurationImportEvents(configurations, exclusions);
   Configurations, exclusions) group
   return new AutoConfigurationEntry(configurations, exclusions);
}
Copy the code

Visible selectImports () is the core of AutoConfigurationImportSelector method

The functions of this method are as follows:

  • To obtainMETA-INF/spring.factoriesIn theEnableAutoConfigurationThe correspondingConfigurationThe class list
  • by@EnableAutoConfigurationIn the annotationsexclude/excludeNameFilter the parameters once
  • By private inner classConfigurationClassFilterFilter once, that is, not satisfied@ConditionalThe configuration of the class
Reading configuration Files

The meta-inf/spring – autoconfigure – metadata. The properties:

Initialize meta-INF/Spring-autoconfigure-metadata. properties in the constructor of the private static inner class ConfigurationClassFilter as follows

ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
   this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
   this.filters = filters;
}
Copy the code

AutoConfigurationMetadataLoader:

final class AutoConfigurationMetadataLoader {

   protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

   private AutoConfigurationMetadataLoader(a) {}static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
      returnloadMetadata(classLoader, PATH); }... }Copy the code

The meta-inf/spring. Factories:

Through getCandidateConfigurations method reads the meta-inf/spring. The class that is configured in the factories:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   // Load all the configuration classes in meta-INF/spring.Factories
   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;
}
Copy the code
public static List<String> loadFactoryNames(Class
        factoryType, @Nullable ClassLoader classLoader) {
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

// This method reads meta-INF /spring.factories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if(result ! =null) {
      return result;
   }

   try {
      // FACTORIES_RESOURCE_LOCATION : 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()) { String factoryTypeName = ((String) entry.getKey()).trim();for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code

extension

For official components, the condition is used to determine whether classes should be auto-assembled or not. For third-party components, the SPI mechanism is used to implement extensions

  • The official package spring – the boot – starter – XXX
  • Third-party package XXX-spring-boot-starter

Both official and third-party components are automatically assembled through the above mechanism.

The core of Springboot’s automatic configuration: passImportSelectorImport configuration classes of components in batches to IOC containers.

Knowledge extension:

@Conditional Details: juejin.cn/post/684490…

@Enable* Module driver details: juejin.cn/post/684490…

ImportSelector batch dynamic import beans: juejin.cn/post/684490…

Original address of the article

Autumn200.com/2020/06/27/…