@EnableAutoConfiguration注解使用和原理

概述

这个注解的源码查看

@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 使用的四种情况:
    1. @Import,最早是为了方便引入java配置类
    2. 参数为@Configuration注解的bean,那么就引入这个配置类。相当于用xml配置时的
    3. 参数为ImportBeanDefinitionRegistrar接口或者ImportSelector接口的实现类,那么就通过接口方法来实现自定义注入
    4. 参数为普通类,直接将该类创建到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 注解的类生效

使用原理

流程图

在这里插入图片描述

实现原理

它的实现原理主要涉及以下几个方面:

  1. 条件化配置
    • @EnableAutoConfiguration 依赖于 @Conditional 注解,使用条件注解来判断是否需要加载某个配置类。

      比如,@ConditionalOnClass 用于检查某个类是否在类路径中,@ConditionalOnMissingBean 用于检查某个 Bean 是否已经存在。

  2. 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 会根据条件动态加载这些配置类
  3. 配置类
    • 自动配置类通常以 @Configuration 注解标记,并定义了许多 @Bean 方法。这些方法会根据条件创建相应的 Bean。
  4. 优先级
    • Spring Boot 允许开发者通过 @AutoConfigureBefore 和 @AutoConfigureAfter 注解来控制自动配置的加载顺序。
  5. 自定义配置
    • 开发者可以通过定义自己的配置类或 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;
      }
      

调用了SpringFactoriesLoader.loadFactoryNames():

SpringFactoriesLoader

loadFactories方法

  1. 首先获取类加载器
  2. 然后调用loadFactoryNames方法获取所有的指定资源的名称集合
  3. 接着调用instantiateFactory方法实例化这些资源类并将其添加到result集合中
  4. 最后调用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
      
      ........
      
加载所有的配置吗

答案肯定是不会的,以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
    • 如果不存在,那么由默认配置来补充
    • 如果已经存在了,自动配置会不满足注解条件,就不会被创建。

有了这两点,可以做到 当我们不做任何配置的时候可以用默认配置来运用新组件,而当我们需要对配置进行调整的时候用自定义的配置来覆盖即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

?abc!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值