SpringBoot自动配置&properties文件配置项优先级

@SpringBootApplication

@SpringBootConfiguration

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
}

指定当前类是一个配置类@Configuration,在启动Spring容器时就会用到该配置类,通过解析该配置类最终会往Spring容器中添加很多的BeanDefinition。



@EnableAutoConfiguration自动配置

SpringBoot的自动配置类主要的实现就是@EnableAutoConfiguration,它的作用有两个:

  • 将配置类的包扫描路径封装成一个bean对象存入Spring容器中
  • 通过@Import+DeferredImportSelector实现解析spring.factories文件中指定的自动配置类,并把这些自动配置类都加载进Spring容器中,只不过这个过程是比较靠后执行的,因为DeferredImportSelector
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
}
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}



@AutoConfigurationPackage该注解的作用就是将配置类包扫描的路径封装成一个Bean,存入Spring容器中,以供第三方来使用。就比如Mybatis的自动配置类会拿这个bean,去进行mapper扫描,去扫描接口+@Mapper



AutoConfigurationImportSelector.class类它是DeferredImportSelector接口的实现类,并重写了getImportGroup()方法

public Class<? extends Group> getImportGroup() {
   // 在DeferredImportSelector接口类型中,如果重写了该方法并且返回不为null,那么则不会调用上方的selectImports()方法
   // 转而去调用这里返回对象中的selectImports()方法
   return AutoConfigurationGroup.class;
}

这里就会去调用AutoConfigurationGroup类的process()方法和selectImports()方法,去读取+过滤spring.factories文件中的自动配置类,并最终将selectImport()方法返回的自动配置类加载进Spring容器并解析

private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
   Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
         () -> String.format("Only %s implementations are supported, got %s",
               AutoConfigurationImportSelector.class.getSimpleName(),
               deferredImportSelector.getClass().getName()));

   // 获取所有自动配置类,并赋值给autoConfigurationEntries和entries
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(annotationMetadata);
   this.autoConfigurationEntries.add(autoConfigurationEntry);

   // 每个自动配置类对应annotationMetadata,selectImports()方法会取出来用
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}


@Override
public Iterable<Entry> selectImports() {
   if (this.autoConfigurationEntries.isEmpty()) {
      return Collections.emptyList();
   }

   Set<String> allExclusions = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());

   Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
         .collect(Collectors.toCollection(LinkedHashSet::new));

   processedConfigurations.removeAll(allExclusions);

   // 给自动配置类排序
   return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
         .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
         .collect(Collectors.toList());
}

去spring.factories文件中取自动配置类详情

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   // 获取@EnableAutoConfiguration的属性
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 获取spring.factories中所有的AutoConfiguration
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 去重(也就是按类名去重)
   configurations = removeDuplicates(configurations);
   // 获取需要排除的AutoConfiguration,可以通过@EnableAutoConfiguration注解的exclude属性,或者spring.autoconfigure.exclude来配置
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);

   // 排除
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);

   // 获取spring.factories中的AutoConfigurationImportFilter对AutoConfiguration进行过滤
   // 默认会拿到OnBeanCondition、OnClassCondition、OnWebApplicationCondition
   // 这三个会去判断上面的AutoConfiguration是否符合它们自身所要求的条件,不符合的会过滤掉,表示不会进行解析了
   // 会利用spring-autoconfigure-metadata.properties中的配置来进行过滤
   // spring-autoconfigure-metadata.properties文件中的内容是利用Java中的AbstractProcessor技术在编译时生成出来的
   configurations = getConfigurationClassFilter().filter(configurations);
   // configurations表示合格的,exclusions表示被排除的,把它们记录在ConditionEvaluationReportAutoConfigurationImportListener中
   fireAutoConfigurationImportEvents(configurations, exclusions);

   // 最后返回的AutoConfiguration都是符合条件的
   return new AutoConfigurationEntry(configurations, exclusions);
}



DeferredImportSelector接口

这是Spring的知识,这里做一个回忆复习

DeferredImportSelect接口是ImportSelect接口的子接口,他们之间的区别如下:

  • ImportSelect接口,解析配置类时在使用@Import注解,如果导入的类型是ImportSelect,所导入的配置类会直接进行解析导入

  • DeferredImportSelect接口,延迟解析,该接口所导入的配置类会在其他配置类解析完之后再进行解析

    DeferredImportSelect接口支持分组,可以实现getImportGroup()方法以及定义Group对象,就相当于指定了DeferredImportSelect所导入进来的配置类所属的组。比如SpringBoot就把所有的自动配置类单独做成了分组AutoConfigurationGroup



@ComponentScan

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
}

主要作用:Spring容器启动时使用配置类,而解析配置类的过程中就会用到这里的@ComponentScan进行包扫描,生成当前配置类所在目录下的所有BeanDefinition。

而这里的excludeFilters添加了两个排除过滤器,

  • 从BeanFactory中找所有的TypeExcludeFilter类型的bean,调用该对象的match()方法进行匹配,如果方法true则表示需要排除

    所以这其实就是一个扩展机制,如果我们想要添加新的过滤机制,就直接实现该接口并且添加进spring容器即可,可以采用容器初始化器在包扫描之前就将它添加进Spring容器。也就是自定义一个类实现ApplicationContextInitializer接口,这个自定义类还需要在spring.factories文件中定义。

  • 排除掉加了@Configuration注解,并且在spring.factories文件中已经定义了的自动配置类。因为这些自动配置类在@EnableAutoConfiguration注解中就会去处理这些自动配置类,不需要这里包扫描进行处理。



条件注解

SpringBoot中的常用条件注解有:

  1. ConditionalOnBean:是否存在某个某类或某个名字的Bean
  2. ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
  3. ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
  4. ConditionalOnClass:是否存在某个类
  5. ConditionalOnMissingClass:是否缺失某个类
  6. ConditionalOnExpression:指定的表达式返回的是true还是false
  7. ConditionalOnJava:判断Java版本
  8. ConditionalOnWebApplication:当前应用是不是一个Web应用
  9. ConditionalOnNotWebApplication:当前应用不是一个Web应用
  10. ConditionalOnProperty:Environment中是否存在某个属性



SpringBootAOP自动配置

源码如下

@Configuration(proxyBeanMethods = false)
// 判断spring.aop.auto这个值是不是为true,如果没有配置则采用matchIfMissing指定的配置项
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    // 当前项目中必须要有Advice这个类才符合这个条件,默认情况下没有该类,需要我们导入spring-boot-starter-aop依赖
    @ConditionalOnClass(Advice.class)
    static class AspectJAutoProxyingConfiguration {

        @Configuration(proxyBeanMethods = false)
        // 在Spring中如果想要使用AOP,那么就需要加上@EnableAspectJAutoProxy注解,其实就是导入一个BeanPostProcessor
        // 在使用ProxyFactory创建代理对象时,就会判断proxyTargetClass这个属性,如果为false则使用JDK的动态代理
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
        static class JdkDynamicAutoProxyConfiguration {

        }

        @Configuration(proxyBeanMethods = false)
        // 在Spring中如果想要使用AOP,那么就需要加上@EnableAspectJAutoProxy注解,其实就是导入一个BeanPostProcessor
        // 在使用ProxyFactory创建代理对象时,就会判断proxyTargetClass这个属性,如果为true则使用cjlib的动态代理
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        // 通过下面这个判断条件可知SpringBoot默认情况下是强制使用的cglib动态代理,除非spring.aop.proxy-target-class=false
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
                               matchIfMissing = true)
        static class CglibAutoProxyConfiguration {

        }

    }
    
    
    

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.aspectj.weaver.Advice")
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
                           matchIfMissing = true)
    static class ClassProxyingConfiguration {

        @Bean
        static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
            return (beanFactory) -> {
                if (beanFactory instanceof BeanDefinitionRegistry) {
                    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                }
            };
        }

    }

}

我们需要导入下面的依赖,当前项目中才会存在Advice这个类,这样才能使用AOP

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.0.9.RELEASE</version>
</dependency>



补充知识

SpringBoot配置项优先级

配置SpringBoot的几个位置:

  • Program arguments 命名行参数:–server.port=8081

  • VM options JVM参数:-Dserver.port=8082

  • Environment variables 操作系统的环境变量:server.port=8083

  • application.properties文件 server.port=8084

    • spring.config.import=/XXX/
    • spring.config.additional-location=/XXX/
    • optional:file:./config/*/
    • optional:file:./config/
    • optional:file:./
    • optional:classpath:/config/
    • optional:classpath:/
    1. 我们还可以在命名行参数/JVM参数/操作系统的环境变量中使用spring.config.import或spring.config.additional-location来指定properties配置文件路径。注意,这两个配置项必须要在这三个位置中的其中一个来进行指定,不能在properties文件中指定,因为此时properties文件还没有进行加载解析
    2. 用户指定的配置文件路径优先级最高,其次默认情况下file目录高于classpath目录,
    3. file目录就是项目的根路径或者是运行jar包时jar包所在的目录;
    4. classpath目录就是使用idea开发时,和java目录平级的resource目录下。java目录和平级的resource目录都是idea为我们更好的开发提供的,编译之后是没有resource目录的,它其中的文件都是直接在classpath目录下的
    5. 某个目录下的properties文件优先级高于yaml文件
    6. optional的作用是我这指定的目录如果不存在也不会报错,在指定的目录下没有找到对应的application.properties文件也不会报错
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值