序言

Spring Boot的出现,得益于“约定大于配置”的理念,没有繁琐的配置、难以集成的内容(大多数流行第三方技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能力。

自动配置作为SpringBoot的精髓,不仅仅是“面试”中会问到自动配置的原理,如果能理解自动配置的原理,将无往不利。

本文使用SpringBoot版本为:2.3.3

工作原理

@SpringBootApplication

首先,我们都知道SpringBoot项目运行标记 @SpringBootApplication 注解的类 的main方法来启动SpringBoot应用,那么从这里开始着手

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

在xxxApplication.java文件中查看@SpringBootApplication注解(按住ctrl+点击注解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SpringBootApplicaion.class
@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的配置类。
    • 标注在某个类上,表示这是一个Spring Boot的配置类。
      • 里面有@Configuration:配置类上标注这个注解。
        配置类就相当于配置文件,配置类也是容器中的一个组件。@Configuration的底层也是@Component
  • @ComponentScan:自动扫描,这里搭配excludeFilters表示跳过扫描
  • @EnableAutoConfiguration:开启自动配置功能,明显这就是自动配置的重点,查看这个注解。

@EnableAutoConfiguration

这个注解也是一个派生注解,@AutoConfigurationPackage和@Import提供了自动配置功能。

其定义如下:

1
2
3
4
5
6
7
8
9
10
// EnableAutoConfiguration.class
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
...
}

@AutoConfigurationPackage

@AutoConfigurationPackage: 自动配置包,查看其定义,可以看到导入了一个Registrar类。

@Import({Registrar.class}):Spring的底层注解@Import,给容器中导入一个组件;导入的组件由Registrar.class指定

20200819104946

查看Registrar类,有个registerBeanDefinitions方法

1
2
3
4
// Registrar.class
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}

registerBeanDefinitions:获取bean的定义

  • 参数一:metadata:获取启动类信息,即获取该类所在包(@SpringBootApplication标记的类所在包)。
  • 参数二:registry:bean注册。
总结

从上述内容中我们可以得出结论:@AutoConfigurationPackage的作用就是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器。

@Import({AutoConfigurationImportSelector.class})

这是自动配置的重点,精髓就在这里,首先查看AutoConfigurationImportSelector类,这个类是配置选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。会给容器中导入非常多的自动配置类(xxxAutoConfiguration)。

可以看到一个selectImports()方法

1
2
3
4
5
6
7
8
9
// AutoConfigurationImportSelector.class
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

20200819110903

如上,这方法的返回值是调用getAutoConfigurationEntry()方法的结果-配置项信息,这个方法就在selectImports()方法下方,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}

20200819111252

这行代码返回的是已申请即将导入容器的配置项信息集合,进入getCandidateConfigurations()方法。

1
2
3
4
5
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

有一行断言:No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.

谷歌翻译:在META-INF / spring.factories中找不到自动配置类。如果使用的是自定义包装,请确保该文件正确。

可以知道是从META-INF / spring.factories找到配置类。

然后进入loadFactoryNames()方法

1
2
3
4
5
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 获取一个传进来的Class的全路径
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

具体细节在loadSpringFactories()方法中,该方法就在下方。

20200819111817

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
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 {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();

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 factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;

for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}

cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}

20200819112033

从 “META-INF/spring.factories” 中获取资源,这与之前那句断言相对应。

20200819112148

spring.factories位置如上,在libraries的Maven: org.springframework.boot: spring-boot-autoconfigure.2.3.3.RELEASE下spring-boot-autoconfigure-2.3.3.RELEASE.jar!文件夹的META-INF中

20200819112439

截取一段内容如上。

继续看loadSpringFactories()方法。

  • 在外层while中会将文件数据转换成URL,然后将URL装换成Properties,Properties是一个Hashtable,最终配置文件中的数据将以key-value的形式存在Properties中的entry中,如下所示:

20200819112653

20200819112722

20200819113147

  • 内层的while循环其实就是对Properties进行遍历,获取key,获取value,并且会将value的值按逗号分隔获取具体的每一个String,然后将这些数据存入result这个Map中。

    key value
    org.springframework.context.ApplicationContextInitializer org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
    org.springframework.context.ApplicationContextInitializer org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    org.springframework.context.ApplicationListener org.springframework.boot.autoconfigure.BackgroundPreinitializer
  • 最后返回该保存了所有配置项信息的map,回到loadFactoryNames()方法

20200819111817

getOrDefault(…)方法是map的方法,用来判断当前map中是否包指定的key,有则返回对应的数据,这个key就是loadFactoryNames()第一个参数的某个Class的全路径名,往回loadFactoryNames方法上一层看

1
2
3
default V getOrDefault(Object key, V defaultValue){

}

20200819114047

如图中所示,传进来的就是EnableAutoConfiguration,那自然这个key就是EnableAutoConfiguration的全路径,也就是会拿到配置文件(spring.factories)中EnableAutoConfiguration所对应的数据

20200819114237

这就是自动配置的配置的组件内容了。

自动配置原理总结

  1. SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration注解
  2. @EnableAutoConfiguration的作用:
    1. 利用@Import({AutoConfigurationImportSelector.class})给容器中导入一些组件
    2. 查看AutoConfigurationImportSelector类中的selectImports方法
    3. 查看selectImports方法调用的getAutoConfigurationEntry方法
    4. 发现通过方法中的List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置
    5. 查看getCandidateConfigurations方法,这个方法返回loadFactoryNames方法的返回值
    6. 查看loadFactoryNames方法,发现返回loadSpringFactories返回后通过getOrDefault筛选的值
    7. loadSpringFactories方法中扫描META-INF/spirng.factories文件内容,将文件中的数据转换成URL,然后将URL装换成Properties,最后对Properties进行遍历,处理数据并存入result这个map中
    8. 通过getOrDefault返回EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中

总结起来就是:

  1. 将类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;
  2. 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中,用他们来做自动配置。
  3. 每一个自动配置类进行自动配置功能

自动配置类进行自动配置示例

HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理:

idea中双击shift搜索HttpEncodingAutoConfiguration

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
@Configuration(
proxyBeanMethods = false
)
// 表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties({ServerProperties.class})
// 启动指定类的ConfigurationProperties功能;将配置文件中对应的值和ServerProperties绑定起来;并把ServerProperties加入到ioc容器中
@ConditionalOnWebApplication(
type = Type.SERVLET
)
// Spring底层@Conditional注解,根据不同条件判断是否生效。
// 例如当前注解:判断当前应用是否是web应用,如果时,配置类生效。
@ConditionalOnClass({CharacterEncodingFilter.class})
// 判断当前项目有没有CharacterEncodingFilter这个类
// CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnProperty(
prefix = "server.servlet.encoding",
value = {"enabled"},
matchIfMissing = true
)
// 判断配置文件中是否存在某个配置 spring.http.encoding.enabled;matchIfMissing = true代表如果不存在,判断也是成立的
// 即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {

// 已经和SpringBoot的配置文件映射了(EnableConfigurationProperties)
private final HttpEncodingProperties properties;

// 只有一个有参构造器的情况下,参数的值就会从ioc容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}

// 给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
// 判断容器中有没有这个组件,没有才配
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.RESPONSE));
return filter;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConfigurationProperties(
prefix = "server",
ignoreUnknownFields = true
)
// 从配置文件中获取指定的值和bean的属性进行绑定
public class ServerProperties {
private Integer port;
private InetAddress address;
@NestedConfigurationProperty
private final ErrorProperties error = new ErrorProperties();
private ServerProperties.ForwardHeadersStrategy forwardHeadersStrategy;
private String serverHeader;
...
}

这些注解根据当前不同的条件判断,决定这个配置类是否生效

一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

配置tomcat接口就是一个应用

1
server.post = 8081

所有在配置文件中能配置的属性都是在xxxxProperties类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类

xxxxAutoConfigurartion:自动配置类,作用是给容器中添加组件
xxxxProperties:作用是封装配置文件中的相关属性;

SpringBoot重点:

  1. SpringBoot启动会加载大量配置类
  2. 编写时看需要的功能有没有默认写好的自动配置类
  3. 再看这个自动配置类写了哪些组件(有需要的组件,就不要配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,可以在配置文件中指定这些属性的值

@Conditional派生注解

自动配置类必须在一定的条件下才能生效的条件靠@Conditional派生注解来判断

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

打印配置报告

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效没生效了;

1
debug=true

添加配置后运行项目就可以在配置台看到配置报告

20200819125947

报告中:

  • CONDITIONS EVALUATION REPORT:自动配置报告
  • Positive matches:自动配置类启用的
  • Negative matches:没有启动,没有匹配成功的自动配置类

参考

SpringBoot+MyBatis+Druid