SpringBoot原理分析

本文简单分析下SpringBoot的原理设计。

SpringBoot

SpringBoot是为了简化构建应用的复杂度。

SpringBoot到底解决了什么问题?


在SpringBoot出现之前

应用构建是建立在大量的XML配置之上,功能模块的依赖全部由开发人员自行整合。

整合的过程复杂不说,还会存在不同的整合方式,稍有不慎就会出现各种奇奇怪怪的问题。

在SpringBoot出现之后

应用构建是建立在大量的starter之上,功能模块的依赖通过maven引入后自动整合。


约定大于配置

约定大于配置是实现SpringBoot的基本原则。

SpringBoot整合了一套默认配置,不需要开发者手动配置XML,只有当默认配置不满足的情况下才需要手动修改默认配置。

四大核心

四大核心包括:auto-configuration(自动配置)starters(起步依赖)cliactuator

auto-configuration

auto-configuration是SpringBoot自动配置的核心,提供模块自动装配功能。

常见的Enable**注解属于自动配置的一部分。

starters

starters是SpringBoot功能整合的关键,按照约定大于配置的原则实现。

例如,spring-boot-starter-webSpring MVC的一种整合,不需要再去配置servlet.xmlweb.xml,仅需要通过改变配置参数即可满足需求。

cli

cli是快速创建原型项目的命令行工具,支持项目创建、编译打包等功能。

cli还支持运行Groovy脚本

actuator

actuator是用于应用的监控与管理的工具,可以查看应用配置及自身环境属性等信息。

actuator提供REST API来查询应用运行时的内部状态。

actuator虽然提供便利,但也存在一定的安全风险,因此一般与spring-boot-start-security一起使用。

自动配置的原理

auto-configuration(自动配置)是SpringBoot自动配置的核心,包括所有Enable**注解。

首先,starters是SpringBoot模块整合的关键,通过简单的maven引入就可以实现模块的整合。

那么starter是如何被引入的?

  1. SpringBoot启动后会去依赖的starter包中查找resources/META-INF/spring.factories文件;

  2. 按照spring.factories来加载AutoConfiture配置类;

  3. 扫描@Configuration注解将配置注入到Context容器中。

其中,spring.factories就是一种约定,SpringBoot按照这种约定来是先模块的自动加载配置。

源码分析
启动注解的扫描过程

@SpringBootApplication是SpringBoot项目的启动注解。

1
2
3
4
5
6
7
8
9
...
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

其中,@EnableAutoConfiguration是启动自动配置的注解。

1
2
3
4
5
6
...
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
....
}

这里,涉及到@ImportImportSelector两个功能点。

@Import 与 ImportSelector

@Import注解用于将指定的实例注入到IOC容器中,等同于XML中的import

@Import支持三种注入方式:直接注入条件注入动态注入

直接注入

通过直接引入目标配置@Import({XXXAutoConfig.class})来实现配置加载。

1
2
3
4
@Import({XXXAutoConfig.class})
@Documented
public @interface EnabletXXX {
}

条件注入

通过实现ImportSelector来选择注入的配置信息。

1
2
3
4
5
6
7
8
9
10
11
@Import({XXXEnableSelector.class})
@Documented
public @interface EnabletXXX {
}

public class XXXEnableSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{XXXAutoConfigConfig.class.getName()};
}
}

动态注入

通过实现ImportBeanDefinitionRegistrar来实现动态注入。

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
@Import({DynamicConfigEnableRegistrar.class})
@Documented
public @interface EnabletXXX {
}

// 需要注入的注解
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface DynamicConfig {
}

// 实现ImportBeanDefinitionRegistrar来实现动态扫描注册
public class DynamicConfigEnableRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

private ResourceLoader resourceLoader;

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 注册扫码器
DynamicRegisterScanner scanner = new DynamicRegisterScanner(registry, false);
// 设置classloader
scanner.setResourceLoader(resourceLoader);
scanner.registerFilters();
// 设置待扫描注解
scanner.addIncludeFilter(new AnnotationTypeFilter(DynamicConfig.class));
// 设置待扫描路径
scanner.doScan("x.y.z.package");
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}

mybatis的mapper扫描就是通过动态注入的方式实现。

扫描并加载配置

SpringBoot通过实现ImportSelector接口来扫描并加载配置。

  • getCandidateConfigurationsMETA-INF/spring.factories获取待配置的类;
  • getAutoConfigurationEntry去重并获取已排除的配置类,并出发ImportEvent事件;
  • 重写selectImports实现配置类的导入。
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
public class AutoConfigurationImportSelector ... {
...
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
...
// 获取自动配置
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
...
// 获取候选配置
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 从META-INF/spring.factories获取自动配置的类
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;
}
...
}

META-INF/spring.factories约定配置格式如下:

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
x.y.z.starter.StarterAutoConfiguration

条件注解

SpringBoot提供了丰富的条件注解来解决依赖加载的问题。


判断Bean是否注册到IOC容器中来确认是否触发当前逻辑,

  • @ConditionalOnBean
  • @ConditionalOnMissingBean

用于解决Bean的依赖问题,当存在某一个依赖模块时自动加载对应的依赖。


判断累加载器中是否存在Class类来确认是否触发当前逻辑,

  • @ConditionalOnClass
  • @ConditionalOnMissingClass

判断生存指定了资源文件来确认是否触发当前逻辑,

  • @ConditionalOnResource

可用于从远程拉取配置文件,实现应用的配置集中管理。


此外,还包括@ConditionalOnJavaConditionalOnNotWebApplicationConditionalOnWebApplication等。