Spring Boot 3.0.x automatic configuration file loading principle

We know that Spring Boot uses the @EnableAutoConfiguration annotation to enable automatic configuration, and generally uses the @SpringBootApplication main annotation that combines it. So how does Spring Boot load configuration files that contain various classes that need to be automatically configured? In this article, we will take a look at the principle of Spring Boot automatic configuration file loading based on Spring Boot 3.0.6.

The source code of the @EnableAutoConfiguration annotation is as follows:

/**
 * 启用Spring应用程序上下文的自动配置,尝试猜测和配置您可能需要的bean。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 允许子类继承
@AutoConfigurationPackage // 注解会将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器
@Import(AutoConfigurationImportSelector.class) // 导入组件,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中
public @interface EnableAutoConfiguration {
    
    

	/**
	 * 环境属性,可以用来覆盖自动配置是否启用。
	 */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * 用于排除某些自动配置类,被排出的自动配置类不会被加载到容器中。
	 */
	Class<?>[] exclude() default {
    
    };

	/**
	 * 根据类名排除自动配置类,被排除的自动配置类不会被加载到容器中。
	 */
	String[] excludeName() default {
    
    };
}

The two most critical annotations above are:

  • @AutoConfigurationPackage: Used to scan all classes in the package where the specified class is located and its subpackages when automatic configuration is enabled. This annotation is typically used on application entry classes so that auto-configuration can automatically scan for packages required by the application. Specifically, this annotation will use the package path of the specified class and all the classes in its subpackages as the basePackage of automatic configuration, and then register it in Spring's BeanFactory so that automatic configuration can scan these packages. all classes.
  • @Import: Import the configuration class, which is the implementation of the ImportSelector interface.

The automatic configuration @Import annotation imports the AutoConfigurationlmportSelector.class class, which is also the key to this annotation, which implements the ImportSelector interface. The ImportSelector interface in Spring Boot is an interface for dynamically importing configuration classes, which can dynamically select the configuration classes that need to be imported according to conditions.

In Spring Boot, we can use the @Import annotation to import configuration classes, but if we need to dynamically select which configuration classes to import based on conditions, we need to use the ImportSelector interface.

The source code of the ImportSelector interface is as follows:

public interface ImportSelector {
    
    
    String[] selectImports(AnnotationMetadata importingClassMetadata);

    @Nullable
    default Predicate<String> getExclusionFilter() {
    
    
        return null;
    }
}

This interface contains two methods:

  • selectImports

    This method is the core method of the ImportSelector interface, which is used to select the configuration class to be imported from the incoming AnnotationMetadata. In this method, you can decide which configuration classes need to be imported according to the annotation information in AnnotationMetadata importingClassMetadata. Returns a String array containing the fully qualified class names of the configuration classes to import.

  • getExclusionFilter

    This method is used to provide a predicate function to filter out some configuration classes that do not need to be imported. The function takes a string argument (that is, the fully qualified class name of the configuration class to import) and returns a boolean indicating whether the configuration class needs to be excluded. If you don't need to do any filtering, you can return nullor a falsepredicate function that always returns .

Then we go back to the AutoConfigurationlmportSelector class, its role is to dynamically import the configuration class according to the automatic configuration class defined in the META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file under the class path.

The source code of the two methods of the ImportSelector interface implemented by the AutoConfigurationlmportSelector class is as follows:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    
    
    // 检查是否启用自动配置
    if (!this.isEnabled(annotationMetadata)) {
    
    
        // 如果未启用,则返回空数组
        return NO_IMPORTS;
    } else {
    
    
        // 获取自动配置条目
        AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        // 返回自动配置条目中的配置类的字符串数组
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

public Predicate<String> getExclusionFilter() {
    
    
    // 返回一个谓词函数,用于过滤应该排除的自动配置类
    return this::shouldExclude;
}

Enter the getAutoConfigurationEntry method, which is used to obtain automatic configuration entries. The source code of the method is as follows:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    
    
    // 检查是否启用自动配置
    if (!this.isEnabled(annotationMetadata)) {
    
    
        // 如果未启用,则返回空的自动配置条目
        return EMPTY_ENTRY;
    } else {
    
    
        // 获取所有候选的自动配置类
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        // 去除重复的自动配置类
        configurations = this.removeDuplicates(configurations);
        // 获取需要排除的自动配置类
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        // 检查需要排除的自动配置类是否存在于候选的自动配置类中
        this.checkExcludedClasses(configurations, exclusions);
        // 从候选的自动配置类中去除需要排除的自动配置类
        configurations.removeAll(exclusions);
        // 应用配置类过滤器,过滤无效的自动配置类
        configurations = this.getConfigurationClassFilter().filter(configurations);
        // 触发自动配置导入事件
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        // 构造自动配置条目并返回
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

We focus on the getCandidateConfigurations method, which is used to obtain candidate auto-configuration classes. The method source code is as follows:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
    
    // 从 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中加载所有自动配置类
    List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
    // 检查自动配置类列表是否为空
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    // 返回自动配置类列表
    return configurations;
}

Among them, the ImportCandidates.load method loads all classes that implement the specified interface from the specified location, and returns an ImportCandidate instance for accessing the loaded classes. Here, this method is used to load all classes that implement the AutoConfiguration interface. this.getBeanClassLoader() is used to obtain the context class loader of the current thread for loading classes. If the auto-configuration class list is empty, an IllegalStateException will be thrown, prompting the user to check whether the auto-configuration file is correct.

The ImportCandidates class mentioned above is an auxiliary class used to load all candidate classes that implement the specified interface. It has a load method for loading all classes that implement the specified interface and returns an instance of ImportCandidates.

The method source code is as follows:

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
    
    
    // 检查注解类是否为空
    Assert.notNull(annotation, "'annotation' must not be null");
    // 获取要使用的类加载器
    ClassLoader classLoaderToUse = decideClassloader(classLoader);
    // 获取要加载的自动配置类列表所在的位置
    String location = String.format("META-INF/spring/%s.imports", annotation.getName());
    // 在类路径中查找指定位置的资源
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
    // 保存所有候选类的列表
    List<String> importCandidates = new ArrayList();
    // 遍历所有找到的资源
    while (urls.hasMoreElements()) {
    
    
        URL url = urls.nextElement();
        // 读取资源中的所有候选类,并添加到列表中
        importCandidates.addAll(readCandidateConfigurations(url));
    }
    // 返回一个包含所有候选类的 ImportCandidates 实例
    return new ImportCandidates(importCandidates);
}

From the source code, we can see that the automatic configuration logic of Spring Boot 3.0.x is to load the automatic configuration in the META-INF/spring/%s.imports configuration file. This %sis to the full class of the @AutoConfiguration annotation Path name, as shown in the following figure:

The content of the configuration file is as follows:

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
...... Spring Boot 3.0.6 共注册了142个自动配置类

It can be seen that the writing format is really simple, and some application-level automatic configuration classes have been moved to this new configuration file. The old automatic configuration file (spring.factories) still retains some system-level components, such as various initializers, listeners, etc. These initializers and listeners will be automatically configured when Spring Boot starts.

Guess you like

Origin blog.csdn.net/ly1347889755/article/details/130466194