Spring Boot运行原理

一、pom.xml

Maven项目先得从pom.xml文件探究起

parent父依赖

在pom.xml中可以看到它主要是依赖于父项目spring-boot-dependencies,这里主要是管理资源过滤及插件的,该父项目还包含一个父依赖spring-boot-dependencies,这里才是真正管理Spring Boot应用里所有依赖版本的地方,也称Spring Boot的版本控制中心

以后导入依赖默认不需要写版本,但若导入的包没有在依赖中管理就需要手动配置版本

启动器 Spring-boot-starter

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

spring-boot-starter-web帮我们导入了web模块正常运行所依赖的组件

Spring Boot将所有的功能场景都抽取出来,封装成一个个启动器,我们只需要在项目中引入这些starter(启动器)即可,所有相关的依赖都会导入进来。如图

二、主启动类

1
2
3
4
5
6
7
@SpringBootApplication
public class HelloworldApplication {

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

就这几行代码完成了一个web项目的启动???

java中最恐怖的莫过于用极其简单的代码完成极其复杂的内容,细思极恐

需了解的核心注解

@SpringBootApplication

1
2
3
4
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@SpringBootApplication注解实际是一个组合注解,除了对应用开放的@ComponentScan注解(实现开发者自定义的应用包扫描)外,其最核心的注解就是@EnableAutoConfiguration,该注解表示开启自动配置功能,而在具体实现上则是通过导入注解@Import({AutoConfigurationImportSelector.class})类的实例,在逻辑上实现了对所依赖核心jar下的META-INF/spring.factories文件的扫描,该文件声明了有哪些自动配置需要被Spring容器加载,从而Spring Boot应用程序就可以自动加载Spring核心容器配置,以及其他依赖的项目组件配置,从而最终完成应用的自动初始化。

@ComponentScan

对应XML配置中的元素,自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中,实现开发者自定义的应用包扫描

@SpringBootConfiguration

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

进入这个注解查看:

1
2
@Configuration
public @interface SpringBootConfiguration {...}

表示这是一个配置类,对应Spring的xml配置文件

点进去得到下面的@Component

1
2
@Component
public @interface Configuration {}

说明启动类本身也是Spring中的一个组件而已,负责启动应用

回到 @SpringBootApplication 注解中继续看。

@EnableAutoConfiguration(重要)

告诉SpringBoot开启自动配置功能,自动载入应用程序所需的所有默认配置。

(插一句)所有自动配置功能都由@AutoConfigurationPackage@Import(EnableAutoConfigurationImportSelector.class)这两个注解来决定。

点进注解继续查看


@AutoConfigurationPackage

自动配置包,将被该注解标识的类所在的包进行扫描。EnableAutoConfiguration被注解了,而该类在org.springframework.boot.autoconfigure包下面,所以会扫描该包下的类,声明bean的就生成bean

@Import(AutoConfigurationPackages.Registrar.class)

实现将主配置类@SpringBootConfiguration标注的所有包及子包里的所有组件扫描到Spring容器中。

  • @Import是Spring底层注解,表示给容器中导入一个组件

  • Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器


@Import(AutoConfigurationImportSelector.class)

给容器中导入组件

实现是SpringBoot在启动时从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置就生效。

AutoConfigurationImportSelector:自动配置导入选择器,使用该类可帮助Spring Boot应用将所有符合条件的@Configuration配置都加载到IOC容器

那么它会导入哪些组件的选择器呢?

三、源码分析

selectImports()

首先执行selectImports()方法

1
2
3
4
5
6
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
···
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

该方法就是用来返回需要导入的组件的全类名数组的,想得到这些数组就得调用getAutoConfigurationEntry()方法

getAutoConfigurationEntry()

获得自动配置的实体

该方法主要作用获取EnableAutoConfiguration中的属性,调用getCandidateConfigurations()方法

1
2
3
4
5
6
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
···
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
···
}

getCandidateConfigurations()

获取候选的配置信息

1
2
3
4
5
6
7
8
9
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

/*getSpringFactoriesLoaderFactoryClass() 方法返回的就是我们最开始看的启动自动导入配置文件的注解类:EnableAutoConfiguration*/
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}

这个方法又调用了 SpringFactoriesLoader 类的静态方法loadFactoryNames()

SpringFactoriesLoader(重要)

SpringFactoriesLoader类的主要功能就是从指定配置文件META-INF/Spring-factories加载配置

spring-factories是一个典型的java properties文件,只不过Key和Value都是Java类型的完整类名

主要作用

  • 扫描所有jar包类路径下META-INF/spring.factories文件

  • 把这些文件内容包装成properties对象,从properties中获取到EnableAutoConfiguration.class类对应的值

  • 然后把它们添加在容器中,将类路径下META-INF/spring.factories里配置的所有EnableAutoConfiguration的值加入到容器中

loadFactoryNames()

获取所有的加载配置类信息

1
2
3
4
5
6
7
8
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories()

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
//获取资源
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//获取资源"META-INF/spring.factories"
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}

result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
//将读取到的资源遍历,封装成为Properties对象
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}

// 将所有列表替换为包含唯一元素的不可修改列表
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}

spring.factories

自动配置根源

在上面的自动配置类中找一个打开

可以看到他们都是JavaConfig配置类,且都注入了Bean

所以,自动配置真正实现是从类路径中搜寻所有的META-INF/spring.factories配置文件,并将其中需要被Spring容器加载的自动配置导入容器,自动配置类就会生效

四、SpringApplication.run分析

SpringApplication类用于引导和启动一个Spring应用程序。回到程序的主入口

1
2
3
4
5
6
7
@SpringBootApplication
public class HelloworldApplication {

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

该方法主要分两部分,一部分是SpringApplication的实例化,一部分是run方法的执行。

SpringApplication

这个类主要做了以下四件事:

  • 推断应用的类型是普通的项目还是Web项目
  • 查找并加载所有可用初始化器,设置到initializers属性中
  • 找出所有的应用程序监听器,设置到listeners属性中
  • 推断并设置main方法的定义类,找到运行的主类

扩展:

深入理解SpringApplication

run方法流程分析

。。。。。。

后面的学习慢慢了解吧!加油!

请我喝杯咖啡吧~

支付宝
微信