乐趣区

SpringBoot基础回顾8

2.2 自动配置(启动流程)

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

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

Spring Boot 应用的启动入口是 @SpringBootApplication 注解标注类中的 main()方法,@SpringBootApplication 能够扫描 Spring 组件并自动配置 Spring Boot

下面,查看 @SpringBootApplication 内部源码进行分析,核心代码具体如下


@SpringBootApplication

public class SpringbootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootDemoApplication.class, args);

   }

}

@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 {...}

 


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

1.@SpringBootConfiguration 注解

@SpringBootConfiguration 注解表示 Spring Boot 配置类。查看 @SpringBootConfiguration 注解源码,核心代码具体如下。


@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Configuration // 配置 IOC 容器

public @interface SpringBootConfiguration {

}


从上述源码可以看出,@SpringBootConfiguration 注解内部有一个核心注解 @Configuration,该注解是 Spring 框架提供的,表示当前类为一个配置类(XML 配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被 Spring Boot 进行了重新封装命名而已

2.@EnableAutoConfiguration 注解


@EnableAutoConfiguration 注解表示开启自动配置功能,该注解是 Spring
Boot 框架最重要的注解,也是实现自动化配置的注解。同样,查看该注解内部查看源码信息,核心代码具体如下

[外链图片转存失败, 源站可能有防盗链机制, 建议将图片保存下来直接上传(img-0iesyK1X-1591594952790)(./images/image-20191226121755878.png)]


可以发现它是一个组合注解,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
{

}


从上述源码可以看出,@AutoConfigurationPackage 注解的功能是由 @Import 注解实现的,它是 spring 框架的底层注解,它的作用就是给容器中导入某个组件类,例如 @Import(AutoConfigurationPackages.Registrar.class),它就是将 Registrar 这个组件类导入到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法,这个方法就是导入组件类的具体实现 :


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

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

(2)@Import({AutoConfigurationImportSelector.class}):AutoConfigurationImportSelector 这个类导入到 spring 容器中,AutoConfigurationImportSelector可以帮助 springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IoC 容器 (ApplicationContext) 中

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

[

深入研究 loadMetadata 方法

[外链图片转存失败, 源站可能有防盗链机制, 建议将图片保存下来直接上传(img-LudWoPn0-1591594952801)(./images/image-20200119172325325.png)]

深入 getCandidateConfigurations 方法

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

继续点开 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;

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


public final class SpringFactoriesLoader {

   
public static final String FACTORIES_RESOURCE_LOCATION =
"META-INF/spring.factories";

}

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

[外链图片转存失败, 源站可能有防盗链机制, 建议将图片保存下来直接上传(img-jxnhPKHT-1591594952805)(./images/image-20191226162644636.png)]

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

以刚刚的项目为例,在项目中加入了 Web 环境依赖启动器,对应的 WebMvcAutoConfiguration 自动配置类就会生效,打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对 Spring MVC 运行所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC 校验器等。而这些自动配置类的本质是传统 Spring MVC 框架中对应的 XML 配置文件,只不过在 Spring Boot 中以自动配置类的形式进行了预先配置。因此,在 Spring Boot 项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,我们也可以对这些自动配置类中默认的配置进行更改

总结

因此 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 加载器进行一系列的容器创建过程

3. @ComponentScan 注解


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

总结:

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


|- @SpringBootConfiguration

   |-
@Configuration  // 通过 javaConfig 的方式来添加组件到 IOC 容器中

|- @EnableAutoConfiguration

   |-
@AutoConfigurationPackage // 自动配置包,与 @ComponentScan 扫描到的添加到 IOC

   |-
@Import(AutoConfigurationImportSelector.class) // 到 META-INF/spring.factories 中定义的 bean 添加到 IOC 容器中

|- @ComponentScan // 包扫描
退出移动版