SpringBoot 自动装配原理

SpringBoot 自动装配原理

  • 背景:

    • 三天前结束了 MyBatis 的学习,SSM框架的学习也告一段落。本想写一个小Demo来锻炼一下巩固一下学习的知识,却发现自己前端的东西一点不会,还是太菜了(ノへ ̄、)

    • 而且在 Github 上找到的项目大部分都是 SpringBoot 为骨架搭建的,没办法还是开启 SpringBoot 的学习。然而事情一般不会这么简单,老师一上课还没有写出 Demo 就直接开始讲底层源码,呕吼,直接亲妈上天(;´д`)ゞ

    • 现在还是经过一下午的整理和晚上视频的学习,再次梳理一遍原理

一、项目结构

Version 2.2.4

image.png

当我们创建项目完成时,将会出现如上图所示的项目结构。

二、入口及其原理分析

其中的 SpringBootApplication就是整个项目的主入口,当我们打开此文件时,就会看到如下的代码

package com.biggun;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 程序的主入口
@SpringBootApplication
public class SpringbootApplication {

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

}

而我们需要重点关注的则是这个 @SpringBootApplication 注解,因为存在这个注解,这个入口才能被 SpringBoot 识别并启动。

为了进行原理分析,我们找到这个接口注解的源代码,可以看到其定义如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  ...
}

现在自动装配的秘密就在我们眼前,主要要分析的还是两个定义

  • @SpringBootConfiguration
    • Spring Boot配置
  • @EnableAutoConfiguration
    • 开启自动配置

1. @SpringBootConfiguration

1.1 源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

可以看到这个注解又定义上了另一个关键注解 @Configuration ,这里的 @Configuration 对我们来说不陌生,它就是 JavaConfig 形式的 Spring Ioc 容器的配置类使用的那个 @ConfigurationSpringBoot 社区推荐使用基于 JavaConfig 的配置形式,所以,这里的启动类标注了 @Configuration 之后,本身其实也是一个 IoC 容器的配置类。同样的,我们打开这个注解看一下

1.2 @Configuration

同样是源码部分

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
  ...
}

可以看到定义中出现了 @Component 注解,表示这个注解的意义为其标注的类会被视为是一个组件,交给 IOC容器接管。

其作用相当于 XML 配置中的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-lazy-init="true">
    <!--bean定义-->
</beans>

在标注了此注解的类中方法可以使用 @Bean ,来起到注册 bean 的作用,等同于

<bean name="bean" class="com.biggun.pojo.Biggun">
  ...
</bean>

image.png

1.3 作用

@SpringBootConfiguration 标注在某个类上,表示这个类是一个 SpringBoot 的配置类,而这个注解由 @Configuration 定义,与其拥有相同的功能,即使被标注的类具有等同于 XML 配置文件的作用,也就是 JavaConfig

而之间的区别在于一个是 SpringBoot 定义的注解,一个是 Spring 定义的注解

2. @EnableAutoConfiguration

相较 Spring 、 SpringMVC ,我们使用 SpringBoot 的时候,不再需要进行任何配置就可以直接使用,这些配置都由 SpringBoot 为我们完成。这个功能就是由 @EnableAutoConfiguration 完成的。

2.1 源码

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

}

可以看到这个注解主要还是由两个部分构成

  • @AutoConfigurationPackage -- 自动配置包

  • @Import(AutoConfigurationImportSelector.class) -- 自动配置导入选择器

2.2 引用

2.2.1 @AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

当我们进入 @AutoConfigurationPackage 的源码时,可以发现,在这个注解的定义中出现了 @Import 注解,这个注解是 Spring 的底层注解,在此处的作用为为 Spring 导入一个组件,导入的组件由 AutoConfigurationPackages.Registrar 类决定

2.2.1.1 AutoConfigurationPackages.Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    register(registry, new PackageImport(metadata).getPackageName());
  }

  @Override
  public Set<Object> determineImports(AnnotationMetadata metadata) {
    return Collections.singleton(new PackageImport(metadata));
  }
}

当我们进入 AutoConfigurationPackages.Registrar 这个静态内部类时,可以发现这个类有两个方法

  • registerBeanDefinitions() -- 注册bean定义
  • determineImports -- 确定入口

我们的关注点主要在 registerBeanDefinitions() 的函数调用上,它通过调用 register 函数来注册 bean 的信息,而且注册 bean 信息的时候需要获取 bean 的元数据 -- metadata ,从而拿到包的名字

为了更加深入的了解其运行原理,我们在此处设置一个断点

image.png

并启动 Debug 模式,可以看到

image.png

元数据中包含了四个项,但我们主要就看两项

  • mergedAnnotation -- 合并注解
  • introspectedClass -- 自检查类

打开其包含的项可以发现 SpringBoot 已经加载到了程序的主入口类,以及入口类所在的包名

此时我们可以计算一下 new PackageImport(metadata).getPackageName() 所得到的信息

image.png

正是主入口类所在的包名

所以可以得出, @EndbleAutoConfiguration 的作用就是将主入口类所在的包下的所有子包中的所有组件导入到 Spring 容器中

故而如果我们将其他的组件不放在主入口类的同级目录下,Spring容器就无法获取到这个组件的相关信息,也就无法访问到

2.2.2 @Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector.class -- 自动配置导入选择器

当我们进入这个类中,我们可以发现有 AutoConfigurationEntry() 这样一个方法,而这个方法的作用则是返回所有需要导入的组件的全类名,这些组件就会被添加到容器中,会给容器导入非常多的自动配置类 XXAutoConfiguration ,就是给容器导入某个而场景所需要的所有组件,并配置好这些组件

在这个方法中调用了一个方法 getCandidateConfiguration(),用来获取候选的配置

image-20200215145109604

Debug 模式下调试就可以看到,当 SpringBoot 执行这个函数时,会获取到如下的组件

image.png

有了自动配置类,就免去了我们编写配置文件和注入功能组件等的工作。

但是 getCandidate() 函数又是如何获取到这些参数的呢?我们就需要打开这个类的定义

image.png

可以发现它主要调用了 SpringFatoriesLoader.loadFactoryNames() 这样一个静态函数,调用了两个值

  • getSpringFactoriesLoaderFactoryClass()
    • return EnableAutoConfiguration.class;
  • getBeanClassLoader()

通过 getSpringFactoriesLoaderFactoryClass() 的定义可知,该函数返回的是一个自动配置的标记,由此标记 Spring 容器才能对其相关的组件进行自动装配

getBeanClassLoader() ,一目了然,这是一个典型的对 POJO 类获取属性值的方法,在此之前必然有其余的函数调用对此属性进行值注入

SpringFatoriesLoader.loadFactoryNames() 的作用尚不明晰,故我们又需要进入这个静态函数查看其定义

image.png

可以发现我们调用的 SpringfatoriesLoader.loadFactoryNames() ,实际上是调用了同类下的一个静态方法 loadSpringFactories()

image.png

在第 133 行,使用类加载器,获取一个静态资源

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

在 139 行,会在类路径下加载完这个静态资源作为一个配置文件 properties ,然后获取到这个配置集合中每个配置的工厂类型名称。

那么总结一下,``SpringBoot启动时调用的 **SpringfatoriesLoader.loadFactoryNames()**会调用SpringfatoriesLoader.loadSpringFactories()在类路径下的META-INF/spring.factories中获取EnableAutoConfiguration` 指定的值,将这些值作为自动配置类导入容器中,自动配置类就生效,免去我们手动配置。

按照这个信息,我们其实可以在项目资源中来到这些信息,而这些信息也就是我们之前计算出来的值。

image.png

SpringMVC 为例,SpringBoot 可以做到开箱即用,以前需要配置的过滤器,DispatcherServlet 等等都不需要我们再手动去配置,做到开箱即用,也是因为在这个 spring.factories 中配置了 WebServicesAutoConfiguration

image.png

当我们进入这个文件中就会发现许多的配置,如 FilterDispatcherServlet等等
image.png

image.png

回到之前 Configurations 的配置计算中,可以发现计算出来的所有组件都是在同一个包目录下的

image.png

同样的,寻找目录,可知这就是 SpringBootJ2EE 应用包的大整合,其整体整合解决方案和自动配置都在 spring-boot-autoconfigure-2.2.4.RELEASE.jar 中了

image.png

以后当我们需要使用对应的模块就只需要在其中找到对应的类,对其中的属性进行配置即可

三、思维导图

@SpringBootApplication

参考视频、资料

狂神说 SpringBoot:https://www.bilibili.com/video/av75233634?p=6

尚硅谷 SpringBoot 权威教程:https://www.bilibili.com/video/av38657363?p=6

Spring Boot干货系列:(三)启动原理解析 http://tengj.top/2017/03/09/springboot3