SpringBoot Auto-assembly Principle

1. SpringBoot auto-assembly principles

Anyone who has worked with SpringBoot must know that there is a @SpringBootApplication annotation on the startup class, and that's the secret to automatic assembly.

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @since 1.2.0
 */
@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 {
  // Omit Code
}

Click on the @SpringBootApplication comment and look at it first to the effect that:

Represents a configuration class that declares one or more methods annotated by @Bean and triggers automatic configuration and component scanning.
It is a more convenient annotation equivalent to @Configuration, @EnableAutoConfiguration, and @ComponentScan.

You can see that it is composed of several notes, the most important of which is three notes:

(1) @SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
  // Omit Code
}

The @SpringBootConfiguration is actually equivalent to @Configuration, which is not explained much.

(2) @EnableAutoConfiguration

This note is the core note for automatic assembly:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  // Omit Code
}

It consists of @AutoConfigurationPackage and @Import(AutoConfigurationImportSelector.class):

1) @AutoConfigurationPackage

/**
 * Registers packages with {@link AutoConfigurationPackages}. When no {@link #basePackages
 * base packages} or {@link #basePackageClasses base package classes} are specified, the
 * package of the annotated class is registered.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see AutoConfigurationPackages
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
  // Omit Code
}

You can see its comment to the effect that:

Register the package with the @AutoConfiguration Package annotation.
When no base package or base package class is specified, the class annotated by it is registered.

It uses the @Import annotation to import AutoConfigurationPackages.Registrar, an internal class that registers scan paths to global variables in containers so that it can be provided to some JPA frameworks to query scan paths.

2) @Import(AutoConfigurationImportSelector.class)

The AutoConfigurationImportSelector class is imported here using @Import, which is the core annotation.

There is a selectImports method in the AutoConfiguration ImportSelector that returns the full paths of all the classes that need to be loaded into the container, and SpringBoot injects them into the container using reflection when it gets the full paths of these classes.

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

So how does selectImports return the full path of these classes?
Focus on the getAutoConfiguration Entry method:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

You can see that in the getAutoConfiguration Entry method, the most important thing is to use the getCandidateConfigurations method to get all the candidate configuration classes:

/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
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;
}

You can see that Spring's SPI mechanism was used to get the full path of the class to load!

Continuing with the code, you can see that in the SpringFactorieLoader class, the configuration in the META-INF/spring.factories file is loaded through the class loader, and if you click on a jar for SpringBoot, such as spring-boot-autoconfigure, you can see that there is actually a META-INF/spring.factories file.

This file is configured with a series of key-value values, so here we're only interested in EnableAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
// Omit Configuration

You should get a general idea of how SpringBoot auto-assembly works by reading the configuration items in META-INF/spring.factories in each jar package to get the full path of the class, then injecting it into the container through @Configuration and @Bean.

(3) @ComponentScan

This annotation should be familiar with setting the scan path, which by default injects beans from the current package and its subpackages into the container.

Tags: Spring Boot

Posted on Wed, 01 Dec 2021 17:28:05 -0500 by RobOgden