The auto-configuration of the Spring Boot Starter is a powerful feature and an important and clever design. But without a clear understanding of how it works, it can be confusing to figure out which automatic configurations are enabled. Here is a look from the source point of view.

Getting started: The concept of automatic configuration

Auto-configuration is not the same as Spring’s Java Configuration. See the definition in the Spring Boot documentation.

  • 16. Auto-configuration

  • 49. Creating Your Own Auto-configuration

Part 0: Start the Spring Boot application

See the reference article.

Part 1: Introduce Spring.Factories

Start with the @ SpringBootApplication

The @SpringBootApplication annotation is defined as follows:

/ / just ahead
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
Copy the code

First look @ ComponentScan part, USES a AutoConfigurationExcludeFilter as exclude filter, literally speaking, is the package package scanning rule out automatic configuration. So, if Spring determines that a class is an auto-configuration class, it does not perform a package scan (to create the declared Bean). Concrete logic is no longer delved into.

The most important annotation here is @enableAutoConfiguration:

// Omit unimportant content
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Copy the code

Focus @ Import (AutoConfigurationImportSelector. Class) this sentence. @import is a familiar substitute for < Import > in XML configuration. However AutoConfigurationImportSelector this class has not been @ Configuration modification, not a Java Configuration class. So it’s weird.

Read the source code should read comments first.

Let’s see what the @import comment reveals:

Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes
Copy the code

Hey! Originally it can import the Java configuration class not only, still can handle ImportSelector and ImportBeanDefinitionRegistrar.

Before we get to the important selector in this section, we should also mention @autoconfigurationPackage:

/**
 * Indicates that the package containing the annotated class should be registered with
 * {@link AutoConfigurationPackages}.
 */
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
Copy the code

While AutoConfigurationPackages. The Registrar’s just ImportBeanDefinitionRegistrar, it has a registerBeanDefinitions interface method, can be directly registered Bean. Behind (extension) as you can see, ImportBeanDefinitionRegistrar is more important, it is various “@ EnableXX annotation” behind the hero.

Ok, now look at the ImportSelector interface:

// omit unimportant comments
/**
 * Interface that determine which @{@link Configuration} class(es) should be imported 
 * based on a given selection criteria, usually one or more annotation attributes.
 */
public interface ImportSelector {
    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}
Copy the code

So, it’s used to determine what Configuration should be imported into a Java Configuration class that is decorated with @Configuration. This is based on all annotation information for the Java configuration class (stored in AnnotationMetadata).

Well, the next step, we take a look at how to implement AutoConfigurationImportSelector.

AutoConfigurationImportSelector class

This class implements the ImportSelector interface as follows:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if(! isEnabled(annotationMetadata)) {return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
    	.loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
    		annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
Copy the code

GetAutoConfigurationEntry method invoked again getCandidateConfigurations method, this method is as follows:

/** * Return the auto-configuration class names that should be considered. By default * this method will load candidates  using {@link SpringFactoriesLoader}
 */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    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

So, at this point, the auto-configuration classes that need to be imported are read from the meta-INF/Spring.Factories file of the package. More detailed code is not posted below, but the SpringFactoriesLoader class.

The next question is, who calls selectImports? To be honest, the call link is so deep that it’s easy to get dizzy. Here is a brief description of the main process.

Part 2: Spring’s Java configuration class loading process

From the ConfigurationClassPostProcessor

As a Spring PostProcessor, this class handles Java Configuration classes decorated with @Configuration. Focus on its core approach:

// omit non-critical code
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    ConfigurationClassParser parser = new ConfigurationClassParser(
    	this.metadataReaderFactory, this.problemReporter, this.environment,
    	this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    parser.parse(candidates);
    parser.validate();
    
    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    // Read the model and create bean definitions based on its content
    if (this.reader == null) {
    	this.reader = new ConfigurationClassBeanDefinitionReader(
    		registry, this.sourceExtractor, this.resourceLoader, this.environment,
    		this.importBeanNameGenerator, parser.getImportRegistry());
    }
    this.reader.loadBeanDefinitions(configClasses);
}
Copy the code

The key logic is to call the Parse method of the ConfigurationClassParser class and then parse and load the internally defined beans for any ConfigurationClass that is parsed.

ConfigurationClassParser#parse()

The parse () method call processConfigurationClass methods mainly.

// omit non-core code
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    	return;
    }
    
    // Recursively process the configuration class and its superclass hierarchy.
    SourceClass sourceClass = asSourceClass(configClass);
    do {
    	sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while(sourceClass ! =null);
    
    this.configurationClasses.put(configClass, configClass);
}
Copy the code

This method does three things:

  1. Determine whether the Java Config class should be skipped during the PARSE_CONFIGURATION phase (Conditional judgment)
  2. calldoProcessConfigurationClass()Do specific analysis
  3. Put the configuration classes for the method parameters inconfigurationClassesIn the

And the second step doProcessConfigurationClass () specific parse what? Is recursively traverses the current Java configuration of @ Import, internal, statement @ ComponentScan etc., when found that other types of Java configuration, called again the parse () method (or processConfigurationClass () method). Parse that class. The end result is that all the Java configurationClasses you can find are added to the configurationClasses container.

Part 3: Look again at the loading process for @SpringBootApplication

Since @SpringBootApplication is really just a Java Configuration class with @Configuration, it will be resolved in the same way, as you can see from Part II.

Annotations and @ SpringBootApplication contains the final import AutoConfigurationImportSelector class, so the class will be called to the. Here is a call to this class of getCandidateConfigurations method mentioned (Part I) when the result of the screenshots:

At this point, all auto-configuration classes defined in the Spring Boot Starter will be scanned and will be parsed.

Supplementary: implementation of @Conditional

In Part 2, already mentioned processConfigurationClass first lines of this method is in the judgment Conditioanal condition is satisfied. If you need to debug why an automatic configuration takes effect or does not take effect, focus on this topic.

Conclusion:

@import is used to Import configuration classes. There are three Import methods.

  1. Import configuration classes directly by@ConfigurationModified class.
  2. ImportSelectorInterface implementation class, returns an array of configuration class names, and then imports those configuration classes.
  3. ImportBeanDefinitionRegistarInterface implementation class that registers beans directly in interface methods.

ImportSelector interface of an implementation class is AutoConfigurationImportSelector contracted for each starter from under the ClassPath in the meta-inf/spring. The factories need to import automatic configuration class read from the file Work.

@ SpringBootApplication annotations are indirectly inherited AutoConfigurationImportSelector function.

In the extended reading, you’ll see how various @enable * annotations work.

The resources

  • Spring Source Code In Depth Analysis, 2nd edition, chapter 14
  • Spring Boot document
  • Read more: This is SpringBoot automatic configuration principle, you should get the idea
  • Read more: How the SpringBoot @enable * annotation works
  • The Spring hook method and hook interface are explained in detail