spring boot 排除自动配置类的方式和原理

spring boot 排除自动配置类的方式和原理

自动配置类可以被以下三种方式排除:

通过 @SpringBootApplication

java 复制代码
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

@SpringBootApplication(excludeName = "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration")

通过 @EnableAutoConfiguration

java 复制代码
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)

@EnableAutoConfiguration(excludeName = "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration")

通过 spring.autoconfigure.exclude 属性配置

yaml 复制代码
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration

原理

自动配置是由 @SpringBootApplication 注解上的 @EnableAutoConfiguration 启用的。@EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector.class) 导入了 AutoConfigurationImportSelector

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

AutoConfigurationImportSelector 中的 getAutoConfigurationEntry 方法用于获取所有的自动配置类:

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 默认为 true
    if (!isEnabled(annotationMetadata)) {
       return EMPTY_ENTRY;
    }
    // 获取 @SpringBootApplication、@EnableAutoConfiguration 中的属性,即 exclude 和 excludeName
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // !!!这是重点,解析 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,获取所有的自动配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 使用 Set 去重
    configurations = removeDuplicates(configurations);
    // 获取应该排除的自动配置类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    // 删除应该排除的自动配置类
    configurations.removeAll(exclusions);
    // 根据 ConditionalOnBean、ConditionalOnClass 的 value 去加载类,若类不存在就可以提前过滤掉一部分
    // 或者根据 ConditionalOnMissingBean、ConditionalOnMissingClass 的 value 去加载类,若类存在就可以提前过滤掉一部分
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        @Nullable AnnotationAttributes attributes) {
    // 解析 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,获取所有的自动配置类
    ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
            getBeanClassLoader());
    List<String> configurations = importCandidates.getCandidates();
    Assert.state(!CollectionUtils.isEmpty(configurations),
            "No auto configuration classes found in " + "META-INF/spring/"
                    + this.autoConfigurationAnnotation.getName() + ".imports. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

ImportCandidates

java 复制代码
public final class ImportCandidates implements Iterable<String> {
	
	private static final String LOCATION = "META-INF/spring/%s.imports";
	
    // 传入的 annotation 参数是 AutoConfiguration.class
	public static ImportCandidates load(Class<?> annotation, @Nullable ClassLoader classLoader) {
		Assert.notNull(annotation, "'annotation' must not be null");
		ClassLoader classLoaderToUse = decideClassloader(classLoader);
        // annotation.getName() 为 org.springframework.boot.autoconfigure.AutoConfiguration
        // LOCATION = "META-INF/spring/%s.imports
        // 格式化结果就是 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
		String location = String.format(LOCATION, annotation.getName());
        // 获取多个 imports 文件的路径
		Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
		List<String> importCandidates = new ArrayList<>();
        // 循环读取 imports 文件
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
            // readCandidateConfigurations 读取文件内容
			importCandidates.addAll(readCandidateConfigurations(url));
		}
		return new ImportCandidates(importCandidates);
	}
    
	private static List<String> readCandidateConfigurations(URL url) {
		try (BufferedReader reader = new BufferedReader(
				new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) {
			List<String> candidates = new ArrayList<>();
			String line;
            // 逐行读取
			while ((line = reader.readLine()) != null) {
				line = stripComment(line);
				line = line.trim();
				if (line.isEmpty()) {
					continue;
				}
                // 一行一个配置类
				candidates.add(line);
			}
			return candidates;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load configurations from location [" + url + "]", ex);
		}
	}
}

AutoConfigurationImportSelector#getExclusions

java 复制代码
public class AutoConfigurationImportSelector {
    
    // spring.autoconfigure.exclude 属性 key
    private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    
	protected Set<String> getExclusions(AnnotationMetadata metadata, @Nullable AnnotationAttributes attributes) {
		Set<String> excluded = new LinkedHashSet<>();
		if (attributes != null) {
            // 添加 @SpringBootApplication、@EnableAutoConfiguration 配置的
			excluded.addAll(asList(attributes, "exclude"));
			excluded.addAll(asList(attributes, "excludeName"));
		}
		// 添加 spring.autoconfigure.exclude 属性配置的
		excluded.addAll(getExcludeAutoConfigurationsProperty());
		return getAutoConfigurationReplacements().replaceAll(excluded);
	}
    
	protected List<String> getExcludeAutoConfigurationsProperty() {
		Environment environment = getEnvironment();
		if (environment == null) {
			return Collections.emptyList();
		}
		if (environment instanceof ConfigurableEnvironment) {
			Binder binder = Binder.get(environment);
            // 从 Environment 中获取 spring.autoconfigure.exclude 的值
			return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
				.map(Arrays::asList)
				.orElse(Collections.emptyList());
		}
        // // 从 Environment 中获取 spring.autoconfigure.exclude 的值
		String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
		return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
	}
}
相关推荐
8Qi82 小时前
LeetCode热题100--45.跳跃游戏 II
java·算法·leetcode·贪心算法·编程
bilI LESS2 小时前
Spring Boot接收参数的19种方式
java·spring boot·后端
九皇叔叔2 小时前
004-SpringSecurity-Demo 拆分环境
java·springboot3·springsecurity
爱滑雪的码农3 小时前
Java八:Character 类与string类
java·开发语言
APIshop3 小时前
京东关键词搜索接口完全指南
java·开发语言·数据库
东离与糖宝3 小时前
HashMap从入门到源码:Java7/8/21区别+面试陷阱+高频追问合集
java·人工智能·面试
wang09073 小时前
Linux性能优化之CPU利用率
java·linux·运维
2601_949817723 小时前
Spring+SpringMVC项目中的容器初始化过程
java·后端·spring
做个文艺程序员4 小时前
Spring AI 1.1 三件套实战:Structured Output + Tool Calling + Memory 从踩坑到生产落地
java·大数据·人工智能