概述
这个注解的源码查看
@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 {};
}
@Import注解,参数是AutoConfigurationImportSelector类。
- @Import 使用的四种情况:
- @Import,最早是为了方便引入java配置类
- 参数为@Configuration注解的bean,那么就引入这个配置类。相当于用xml配置时的
- 参数为ImportBeanDefinitionRegistrar接口或者ImportSelector接口的实现类,那么就通过接口方法来实现自定义注入
- 参数为普通类,直接将该类创建到Spring的IoC容器、
- @Import(AutoConfigurationImportSelector.class):这里的AutoConfigurationImportSelector类属于第三种情况,实现了ImportSelector接口。ImportSelector接口只声明了一个方法,要求返回一个包含类全限定名的String数组,这些类将会被Spring添加到IoC容器。
- 借助AutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。
- 借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才成功
- 在AutoConfigurationImportSelector类中可以看到通过 SpringFactoriesLoader.loadFactoryNames()把 spring-boot-autoconfigure.jar/META-INF/spring.factories中每一个xxxAutoConfiguration文件都加载到容器中,
- 对于@EnableAutoConfiguration来说,SpringFactoriesLoader的用途稍微不同一些,其本意是为了提供SPI扩展的场景,而在@EnableAutoConfiguration场景中,它更多提供了一种配置查找的功能支持,即
根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfig.EnableAutoConfiguration作为查找的Key,获得对应的一组@Configuration类
。
作用
如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。说白了 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入
。
@EnableConfigurationProperties注解的作用是:让使用 @ConfigurationProperties 注解的类生效
使用原理
流程图
实现原理
它的实现原理主要涉及以下几个方面:
- 条件化配置:
- @EnableAutoConfiguration 依赖于 @Conditional 注解,使用条件注解来判断是否需要加载某个配置类。
比如,@ConditionalOnClass 用于检查某个类是否在类路径中,@ConditionalOnMissingBean 用于检查某个 Bean 是否已经存在。
- @EnableAutoConfiguration 依赖于 @Conditional 注解,使用条件注解来判断是否需要加载某个配置类。
- SpringFactoriesLoader:
加载自动配置类
- Spring Boot 使用 SpringFactoriesLoader 从 META-INF/spring.factories 文件中加载自动配置类。这个文件列出了所有的自动配置类,Spring Boot 会根据条件来选择性地加载这些类。
- 这个文件包含了所有自动配置类的全限定名。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ ...
动态加载
:Spring Boot 会根据条件动态加载这些配置类
配置类
:- 自动配置类通常以 @Configuration 注解标记,并定义了许多 @Bean 方法。这些方法会根据条件创建相应的 Bean。
优先级
:- Spring Boot 允许开发者通过 @AutoConfigureBefore 和 @AutoConfigureAfter 注解来控制自动配置的加载顺序。
自定义配置
:- 开发者可以通过定义自己的配置类或 Bean 来覆盖自动配置的默认行为。
核心点:@EnableAutoConfiguration 的核心在于条件化配置和动态加载
,使得 Spring Boot 能够根据项目的依赖和环境自动配置应用程序的上下文,从而大大简化了配置过程。
AutoConfigurationImportSelector源码
这部分代码是 Spring Boot 中自动配置的核心部分,主要涉及到如何选择和导入自动配置类。下面是主要代码的详细解析
selectImports
- 功能:该方法
用于选择要导入的配置类
。 - 参数:annotationMetadata 是当前注解的元数据。
- 逻辑:
- 首先调用
isEnabled(annotationMetadata)
方法检查当前的自动配置是否启用
。如果未启用,返回 NO_IMPORTS,表示不导入任何配置。 - 如果启用,调用
getAutoConfigurationEntry(annotationMetadata) 方法获取自动配置条目
。 - 最后,将获取到的配置类转换为字符串数组并返回。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 检查自动配置是否启用,如果未启用,返回不导入任何配置 if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; // NO_IMPORTS 是一个常量,表示不导入任何配置 } // 获取当前注解的自动配置条目,包括候选配置类和排除的配置类 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); // 将候选配置类转换为字符串数组并返回 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
- 首先调用
getAutoConfigurationEntry
- 功能:该方法用于
获取自动配置条目,包括候选配置类和排除的配置类
。通过getCandidateConfigurations方法获取配置类的路径
,然后去重排序一系列处理之后返回出去交给Spring去处理。 - 逻辑:
- 首先检查自动配置是否启用,如果未启用,返回 EMPTY_ENTRY。
- 获取注解的属性,调用 getAttributes(annotationMetadata)。
- 获取候选配置类列表,调用 getCandidateConfigurations(annotationMetadata, attributes)。
- 使用 removeDuplicates(configurations) 方法去重。
- 获取排除的配置类,调用 getExclusions(annotationMetadata, attributes)。
- 调用 checkExcludedClasses(configurations, exclusions) 方法检查排除的类是否在候选配置中。
- 从候选配置中移除排除的类。
- 使用 getConfigurationClassFilter().filter(configurations) 方法过滤配置类。
- 触发自动配置导入事件,调用 fireAutoConfigurationImportEvents(configurations, exclusions)。
- 最后,返回一个 AutoConfigurationEntry 对象,
包含候选配置类和排除的配置类
。protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { // 检查自动配置是否启用,如果未启用,返回一个空的自动配置条目 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; // EMPTY_ENTRY 是一个常量,表示没有配置 } // 获取注解的属性,例如 @EnableAutoConfiguration 的属性 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); }
getCandidateConfigurations
- 功能:
获取候选的自动配置类
,这些类将被用于自动配置 Spring Boot 应用程序的上下文。- 动态加载和返回候选的自动配置类,确保 Spring Boot 应用能够根据项目的依赖和环境进行适当的自动配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 创建一个列表,用于存储候选的自动配置类 List<String> configurations = new ArrayList<>( // 从 META-INF/spring.factories 文件中加载自动配置类的名称 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()) ); // 从 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中加载更多的自动配置类 ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add); // 确保候选配置类列表不为空,如果为空则抛出异常 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor 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; }
- 动态加载和返回候选的自动配置类,确保 Spring Boot 应用能够根据项目的依赖和环境进行适当的自动配置
调用了SpringFactoriesLoader.loadFactoryNames():
SpringFactoriesLoader
loadFactories方法
- 首先获取类加载器
- 然后调用loadFactoryNames方法获取所有的指定资源的名称集合
- 接着调用instantiateFactory方法实例化这些资源类并将其添加到result集合中
- 最后调用AnnotationAwareOrderComparator.sort方法进行集合的排序
SpringFactoriesLoader是一个抽象类,类中定义的静态属性定义了其加载资源的路径public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
,此外还有三个静态方法:
- loadFactories:加载指定的factoryClass并进行
实例化
。 - loadFactoryNames:加载指定的factoryClass的
名称集合
。 - instantiateFactory:对指定的factoryClass进行
实例化
。
loadFactoryNames()
- loadFactoryNames 方法:用于
根据指定的工厂类型加载对应的实现类名称
。 - loadSpringFactories 方法:负责
从 META-INF/spring.factories 文件中加载所有工厂配置,并返回一个包含工厂类型及其实现类名称的映射
。通过缓存机制提高性能,并处理可能的异常情况。
loadFactoryNames 方法
功能
- 加载指定类型的工厂名称:该方法根据传入的工厂类型(factoryType)和类加载器(classLoader)加载对应的实现类名称。
- 返回实现类名称列表:返回一个包含所有实现类名称的列表,如果没有找到则返回空列表
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 使用提供的类加载器,如果为 null,则使用 SpringFactoriesLoader 的类加载器
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 获取工厂类型的全限定名
String factoryTypeName = factoryType.getName();
// 从加载的 Spring 工厂中获取对应工厂类型的实现类名列表,如果没有则返回空列表
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories 方法
功能
- 加载所有工厂配置:该方法从 META-INF/spring.factories 文件中加载所有工厂配置,并将其存储在一个 Map 中,键为工厂类型名称,值为实现类名称的列表。
- 缓存机制:使用缓存来避免重复加载,提高性能。
- 处理异常:如果在加载过程中发生 IOException,则抛出一个 IllegalArgumentException,并提供详细的错误信息。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 从缓存中获取结果,如果存在则直接返回
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
// 如果缓存中没有,则创建一个新的结果 Map
Map<String, List<String>> result = new HashMap();
try {
// 获取所有 META-INF/spring.factories 文件的 URL
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
// 遍历所有找到的 URL
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加载 properties 文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
// 遍历 properties 中的每个条目
while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim(); // 获取工厂类型名称
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); // 获取实现类名称数组
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
// 遍历实现类名称数组,将其添加到结果 Map 中
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
// 去重并将结果转换为不可变列表
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
// 将结果缓存
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
IOException ex = var14;
// 如果加载过程中出现异常,抛出 IllegalArgumentException
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", ex);
}
}
}
工程中一共有2个META-INF/spring.factories文件,
- 一个在spring-boot包中,
- 一个在spring-boot-autoconfigure包中。看下spring-boot-autoconfigure包中的内容(片段):
- spring-boot-autoconfigure为我们引入了一大堆的java配置类作为组件的默认配置。
- 包括常见组件的默认配置,有rabbit相关的,有redis相关的,有Security相关的等等。
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener ........
- spring-boot-autoconfigure为我们引入了一大堆的java配置类作为组件的默认配置。
加载所有的配置吗
答案肯定是不会的,以RedisAutoConfiguration为例
- 源码如下
// 标记该类为自动配置类,Spring Boot 会根据条件自动加载此配置
@AutoConfiguration
// 仅在类路径中存在 RedisOperations 类时加载此配置
@ConditionalOnClass(RedisOperations.class)
// 启用对 RedisProperties 类的配置属性绑定
@EnableConfigurationProperties(RedisProperties.class)
// 导入其他配置类,配置 Redis 的连接工厂
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
// 定义一个名为 redisTemplate 的 Bean
@Bean
// 仅在 Spring 容器中不存在名为 redisTemplate 的 Bean 时创建
@ConditionalOnMissingBean(name = "redisTemplate")
// 仅在存在且只有一个 RedisConnectionFactory Bean 时创建
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 创建 RedisTemplate 实例
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory); // 设置连接工厂
return template; // 返回 RedisTemplate 实例
}
// 定义一个名为 stringRedisTemplate 的 Bean
@Bean
// 仅在 Spring 容器中不存在 StringRedisTemplate Bean 时创建
@ConditionalOnMissingBean
// 仅在存在且只有一个 RedisConnectionFactory Bean 时创建
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 创建并返回 StringRedisTemplate 实例
return new StringRedisTemplate(redisConnectionFactory);
}
}
这里利用了Spring条件注解的特性,通过设定一定的条件来实现不同场景下加载不同的配置。
是否要注入Bean:
自动配置类生效的条件通常是我们引入了相关的组件,如果没有引入组件,那么就算包含在spring.factories文件中也不会被加载
。是否要注入Bean则要看当前上下文中是否已经存在相应的Bean
。- 如果不存在,那么由默认配置来补充。
- 如果已经存在了,自动配置会不满足注解条件,就不会被创建。
有了这两点,可以做到 当我们不做任何配置的时候可以用默认配置来运用新组件,而当我们需要对配置进行调整的时候用自定义的配置来覆盖即可
。