• 06.倾情奉献之深入分析SpringBoot自动配置


    在这里插入图片描述

    SpringBoot自动配置

    概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目

    问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

    Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,

    @SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。

    • 查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下
    @SpringBootApplication
    public class SpringbootDemoApplication {
     		public static void main(String[] args) {
       			SpringApplication.run(SpringbootDemoApplication.class, args);
     	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    进入到 @SpringBootApplication 内,观察其做了哪些工作:

    @Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
    @Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
    @Documented //表示注解可以记录在javadoc中
    @Inherited  //表示可以被子类继承该注解
    @SpringBootConfiguration   // 标明该类为配置类
    @EnableAutoConfiguration   // 启动自动配置功能
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes =
    TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes =
    AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    // 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    // 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全
    类名字符串数组。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
    // 指定扫描包,参数是包名的字符串数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    // 扫描特定的包,参数类似是Class类型数组。
    @AliasFor(annotation = ComponentScan.class, attribute =
    "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、
    @ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下:

    1.@SpringBootConfiguration注解

    @SpringBootConfiguration : SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot的配置类。

    查看@SpringBootConfiguration注解源码,核心代码具体如下。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象
    public @interface SpringBootConfiguration {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见:

    @SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已

    2.@EnableAutoConfiguration注解

    @EnableAutoConfiguration :开启自动配置功能,以前由我们需要配置的东西,现在由 SpringBoot帮我们自动配置,这个注解就是 Springboot 能实现自动配置的关键。

    同样,查看该注解内部查看源码信息,核心代码具体如下

    // 自动配置包
    @AutoConfigurationPackage
    // Spring的底层注解@Import,给容器中导入一个组件;
    // 导入的组件是AutoConfigurationPackages.Registrar.class
    @Import(AutoConfigurationImportSelector.class)
    // 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
    public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    // 返回不会被导入到 Spring 容器中的类
    Class<?>[] exclude() default {};
    // 返回不会被导入到 Spring 容器中的类名
    String[] excludeName() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以发现它是一个组合注解, Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import来收集并注册特定场景相关的 Bean ,并加载到 IOC 容器。@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。

    下面,对这两个核心注解分别讲解 :

    (1)@AutoConfigurationPackage注解

    查看@AutoConfigurationPackage注解内部源码信息,核心代码具体如下:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({Registrar.class})    // 导入Registrar中注册的组件
    public @interface AutoConfigurationPackage {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从上述源码可以看出,@AutoConfigurationPackage注解的功能是由@Import注解实现的,它是spring框架的底层注解,它的作用就是给容器中导入某个组件类。

    如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefinitions方法,这个方法就是导入组件类的具体实现 :image-20220625130015155

    从上述源码可以看出,在Registrar类中有一个registerBeanDefinitions()方法,使用Debug模式启动项目, 可以看到选中的部分就是com.lagou。也就是说,@AutoConfigurationPackage注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到spring容器中。

    因此 在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫

    (2)@Import({AutoConfigurationImportSelector.class})注解

    将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。

    继续研究AutoConfigurationImportSelector这个类,通过源码分析这个类中是通过selectImports这个方法告诉springboot都需要导入那些组件

    image-20220625130151405

    深入研究loadMetadata方法

    image-20220625130306872

    AutoConfigurationImportSelector类 getAutoConfigurationEntry方法

    protected AutoConfigurationEntry
    getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
    AnnotationMetadata annotationMetadata) {
        //判断EnabledAutoConfiguration注解有没有开启,默认开启
    if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
    }
        //获得注解的属性信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //获取默认支持的自动配置类列表
    List<String> configurations =
    getCandidateConfigurations(annotationMetadata, attributes);
        //去重
    configurations = removeDuplicates(configurations);
        //去除一些多余的配置类,根据EnabledAutoConfiguratio的exclusions属性进行排除
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
        //根据pom文件中加入的依赖文件筛选中最终符合当前项目运行环境对应的自动配置类
    configurations = filter(configurations, autoConfigurationMetadata);
        //触发自动配置导入监听事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    深入getCandidateConfigurations方法
    这个方法中有一个重要方法loadFactoryNames,这个方法是让SpringFactoryLoader去加载一些组件的名字。

    image-20220625130347343

    继续点开loadFactory方法

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable
    ClassLoader classLoader) {
       
        //获取出入的键
        String factoryClassName = factoryClass.getName();
        return
    (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,
    Collections.emptyList());
     }
      private static Map<String, List<String>> loadSpringFactories(@Nullable
    ClassLoader classLoader) {
        MultiValueMap<String, String> result =
    (MultiValueMap)cache.get(classLoader);
        if (result != null) {
          return result;
       } else {
          try {
          
            //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的
    配置类的全路径信息封装 为Enumeration类对象
            Enumeration<URL> urls = classLoader != null ?
    classLoader.getResources("META-INF/spring.factories") :
    ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();
            //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的
    键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
            while(urls.hasMoreElements()) {
              URL url = (URL)urls.nextElement();
              UrlResource resource = new UrlResource(url);
              Properties properties =
    PropertiesLoaderUtils.loadProperties(resource);
              Iterator var6 = properties.entrySet().iterator();
                while(var6.hasNext()) {
                Entry<?, ?> entry = (Entry)var6.next();
                String factoryClassName =
    ((String)entry.getKey()).trim();
                String[] var9 =
    StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                int var10 = var9.length;
                for(int var11 = 0; var11 < var10; ++var11) {
                  String factoryName = var9[var11];
                  result.add(factoryClassName, factoryName.trim());
               }
             }
           }
            cache.put(classLoader, result);
            return result;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    会去读取一个 spring.factories 的文件,读取不到会表这个错误,我们继续根据会看到,最终路径的长这样,而这个是spring提供的一个工具类

    public final class SpringFactoriesLoader {
      public static final String FACTORIES_RESOURCE_LOCATION = "META-
    INF/spring.factories";
    }
    
    • 1
    • 2
    • 3
    • 4

    它其实是去加载一个外部的文件,而这文件是在

    image-20220625130448623

    image-20220625130452533

    @EnableAutoConfiguration就是从classpath中搜寻META-INF/spring.factories配置文件,并将其中
    org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(JavaRefletion)实例化为对应的标注了@Configuration的JavaConfig形式的配置类,并加载到IOC容器中

    以刚刚的项目为例,在项目中加入了Web环境依赖启动器,对应的WebMvcAutoConfiguration自动配置类就会生效,打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对Spring MVC运行所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC校验器等。

    而这些自动配置类的本质是传统Spring MVC框架中对应的XML配置文件,只不过在Spring Boot中以自动配置类的形式进行了预先配置。因此,在Spring Boot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,我们也可以对这些自动配置类中默认的配置进行更改

    3. @ComponentScan注解

    @ComponentScan注解具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到Spring Boot项目主程序启动类所在包的具体位置。

    总结:
    @SpringBootApplication 的注解的功能就分析差不多了, 简单来说就是 3 个注解的组合注解:

    image-20220625130721549

    总结

    因此springboot底层实现自动配置的步骤是:

    1. springboot应用启动;
    2. @SpringBootApplication起作用;
    3. @EnableAutoConfiguration;
    4. @AutoConfigurationPackage:这个组合注解主要是
      @Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而
      Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的
      容器中;
    5. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector
      类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程
      中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-
      INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创
      建过程

    💖💖💖 完结撒花

    💖💖💖 路漫漫其修远兮,吾将上下而求索

    💖💖💖 写作不易,如果您觉得写的不错,欢迎给博主点赞、收藏、评论来一波~让博主更有动力吧

  • 相关阅读:
    ARM 架构、ARM7、ARM9、STM32、Cortex M3 M4 、51、AVR 有啥区别
    在 Windows 上开发.NET MAUI 应用_1.安装开发环境
    浏览器本地储存
    领导:谁再用redis过期监听实现关闭订单,立马滚蛋!
    挑战30天学完Python:Day1火力全开-初识Python(含系列大纲)
    tx.origin 与 msg.sender
    SpringMVC实现增删改查(CRUD)--从头到尾全面详细讲解
    STM32CubeMX串口通讯
    在windows和linux上玩转Tensorrt
    Pandas 数据排序
  • 原文地址:https://blog.csdn.net/qq_41239465/article/details/125458194