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();
	}
}
相关推荐
Wenzar_18 小时前
**零信任架构下的微服务权限控制:用Go实现基于JWT的动态访问策略**在现代云原生环境中,
java·python·微服务·云原生·架构
lUie INGA1 天前
在2023idea中如何创建SpringBoot
java·spring boot·后端
geBR OTTE1 天前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端
Porunarufu1 天前
博客系统UI自动化测试报告
java
of Watermelon League1 天前
SpringBoot集成Flink-CDC,实现对数据库数据的监听
数据库·spring boot·flink
Aurorar0rua1 天前
CS50 x 2024 Notes C - 05
java·c语言·数据结构
Cosmoshhhyyy1 天前
《Effective Java》解读第49条:检查参数的有效性
java·开发语言
布谷歌1 天前
常见的OOM错误 ( OutOfMemoryError全类型详解)
java·开发语言
eLIN TECE1 天前
springboot和springframework版本依赖关系
java·spring boot·后端
老神在在0011 天前
Spring Bean 的六种作用域详解
java·后端·spring