为什么 spring boot 自动配置类不应该被 ComponentScan 扫描到
为什么 spring boot 自动配置类不应该被 `ComponentScan` 扫描到
在 Creating Your Own Auto-configuration :: Spring Boot 中有以下内容:
NOTE
Auto-configurations must be loaded only by being named in the imports file. Make sure that they are defined in a specific package space and that they are never the target of component scanning. Furthermore, auto-configuration classes should not enable component scanning to find additional components. Specific
@Importannotations should be used instead.
自动配置只能通过在 imports 文件(META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)中指定其名称的方式进行加载。请确保它们被定义在特定的包空间内,并且绝不能成为组件扫描的目标。此外,自动配置类不应启用组件扫描以查找其他组件。应使用特定的 @Import 注解来替代。
如何才能使自动配置类被 ComponentScan 扫描到
默认使用 @SpringBootApplication 注解时,ComponentScan不会扫描自动配置类,因为 SpringBootApplication 注解上的 @ComponentScan 配置了一个 AutoConfigurationExcludeFilter 类型的 excludeFilters,它会在组件扫描时排除掉在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中注册的和有 @AutoConfiguration 注解的类。
@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 {}
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
@SuppressWarnings("NullAway.Init")
private ClassLoader beanClassLoader;
private volatile @Nullable List<String> autoConfigurations;
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// 如果有 @Configuration 注解且是自动配置类
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
private boolean isConfiguration(MetadataReader metadataReader) {
// 是否有 @Configuration
return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}
private boolean isAutoConfiguration(MetadataReader metadataReader) {
// 有 @AutoConfiguration
boolean annotatedWithAutoConfiguration = metadataReader.getAnnotationMetadata()
.isAnnotated(AutoConfiguration.class.getName());
return annotatedWithAutoConfiguration
// 存在于 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中
|| getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}
protected List<String> getAutoConfigurations() {
List<String> autoConfigurations = this.autoConfigurations;
if (autoConfigurations == null) {
// 加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件
ImportCandidates importCandidates = ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader);
autoConfigurations = importCandidates.getCandidates();
this.autoConfigurations = autoConfigurations;
}
return autoConfigurations;
}
}
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);
}
}
}
在自定义配置类中添加无 AutoConfigurationExcludeFilter 的 ComponentScan 即可扫描到自动配置类:
@Configuration
@ComponentScan(basePackages = {"org.springframework.boot.autoconfigure.**"})
public class TestConfiguration {
}
可能的原因
spring 只说了自动配置类绝不能成为组件扫描的目标,但没有说明原因,以下是我认为可能的原因。
违背了自动配置的初衷
Spring Boot 中存在两套独立的“发现与加载”机制,它们的执行时机和目的截然不同:
- 组件扫描,属于用户配置阶段,由
@ComponentScan控制:- 目的:用于发现用户或者说应用相关的配置、Bean,比如
@Controller,@Service,@Component等用户自己写的代码。 - 时机:较早期,且一般是无条件的。
- 范围:会无脑扫描
basePackages下的所有文件。
- 目的:用于发现用户或者说应用相关的配置、Bean,比如
- 自动配置,属于自动配置阶段,由
@EnableAutoConfiguration控制:- 目的:用于发现基础设施相关的配置、Bean(框架、第三方提供的能力)。
- 时机:在用户配置阶段之后,这时用户定义的
BeanDefinition都已经加载完毕,可以作为自动配置的@Conditional的判断依据了。 - 范围:只会扫描
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中定义的类。
@ConditionalOnMissingBean、@ConditionalOnProperty 条件注解失效
@ConditionalOnMissingBean 当不存在某个 Bean 时才执行配置。
@ConditionalOnProperty 当属性满足某个条件时才执行配置,依赖于 Environment。
正常流程:
用户配置阶段加载用户 BeanDefinition;处理用户 @PropertySource,将其添加到 Environment 中。
自动配置阶段根据已经加载的用户 BeanDefinition 来判断是否满足 @ConditionalOnMissingBean;根据 Environment 判断是否满足 @ConditionalOnProperty 。
被扫描后:
自动配置类和用户配置类混在一起,处理顺序无法保证,可能出现以下情况:
此时用户 Bean 还没创建 → @ConditionalOnMissingBean 判断为不存在 → 自动配置先创建 Bean
→ 后续用户配置加载时,发现 Bean 已存在 → 报错:Bean 重复定义。
此时用户 @PropertySource 还没处理 → @ConditionalOnProperty 判断条件不满足 → 自动配置不会执行 → 处理用户 @PropertySource 使 @ConditionalOnProperty 条件变为满足。这会导致本该执行的自动配置没被执行,反之亦然。
@EnableConfigurationProperties 绑定空值
与 @ConditionalOnProperty 类似,@EnableConfigurationProperties 也依赖于 Environment。@EnableConfigurationProperties 先于配置文件处理会导致绑定空值。
自动配置的 exclude 失效
自动配置类可以被以下方式排除,但是组件扫描不会考虑这些排除配置,这会导致自动配置类的排除失效。
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@SpringBootApplication(excludeName = "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration")
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
@EnableAutoConfiguration(excludeName = "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration")
spring:
autoconfigure:
exclude:
- org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration
自动配置类的顺序失效
自动配置类可以通过 @AutoConfigureBefore、@AutoConfigureAfter、@AutoConfiguration(before, beforeName, after, afterName) 注解定义自动配置的执行顺序与依赖关系。组件扫描不会考虑这些排序注解,导致处理顺序混乱。
比如 MybatisAutoConfiguration 有注解 @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })。如果 MybatisAutoConfiguration 先于 DataSourceAutoConfiguration 被处理会导致 @ConditionalOnSingleCandidate(DataSource.class) 条件不满足,导致 Mybatis 没被自动配置,进而导致应用启动失败。
自动配置类被处理两遍
若自动配置类被组件扫描则会被处理两遍,第一遍是被组件扫描处理,第二遍是被自动配置处理。一方面浪费资源,另一方面可能导致未知问题。
产生不必要的性能开销
自动配置类通常位于第三方 Jar 包中(如 spring-boot-autoconfigure)。如果组件扫描不加过滤地去扫描所有 Jar 包,会极大地拖慢启动速度。
更多推荐



所有评论(0)