Spring Boot一大特点就是自动约定配置,很多情况下可以写很少的配置甚至不写配置就可以运行程序,其中的原理是需要我们理解的。我们可以大致猜测一下,以前我们没有使用Spring Boot的时候,比如以前搭建SSM框架时,一切入口是Spring开始,当需要使用MyBatis时,需要在配置各种MyBatis的Bean,然后Spring启动的时候就能加载这些Bean,运行时,应用就能直接使用这些Bean。现在使用Spring Boot了,他自己帮我们将一些Bean加载进Spring IOC容器中,如果需要配置,也会给出默认配置,以保证正常启动和基本功能。我们需要了解的是,Spring Boot是如何判断哪些Bean需要加载,以及这些Bean是如何加载的。
一、Spring Boot2自动配置原理
Spring Boot关于自动配置的功能在
spring-boot-autoconfigure-X
这个jar中,当然还要结合启动类上的注解,才能将整个过程串起来
@SpringBootApplication
public class App{
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
它主要加载了@SpringBootApplication注解主配置类,这个@SpringBootApplication注解主配置类里边最主要的功能就是Spring Boot开启了一个@EnableAutoConfiguration注解的自动配置功能。
@EnableAutoConfiguration
public @interface SpringBootApplication {
...
}
2、@EnableAutoConfiguration作用:
它主要利用了一个
EnableAutoConfigurationImportSelector选择器给Spring容器中来导入一些组件。
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
3、那么导入了哪些组件呢?
我们来看EnableAutoConfigurationImportSelector#selectImports(),selectImports()方法的作用就是用来挑选需要导入Spring Container的组件,
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 读取 spring-boot-autoconfigration.jar的
// META-INF/spring-autoconfigure-metadata.properties文件到java.util.Properties中
// 先筛选一下要加载哪些配置类,再把符合条件配置类的信息封装到AutoConfigurationMetadata
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 读取 META-INF/spring.factories文件,把其信息封装到
// AutoConfigurationEntry里,再将步骤一获取的配置类与此处获取的配置类
// 执行过滤、去重、排序等操作,保存满足条件的配置类
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
// 返回满足自动配置条件的类的字符串数组,后交给@Import注解,将其注入到Spring容器中,
// 至此,完成SpringBoot的自动配置!
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(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) {
// 这里加载了factory,加载的是spring-boot-autoconfigration.jar的
// 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;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
加入到容器中之后的作用就是用它们来做自动配置,这就是Springboot自动配置之源,也就是自动配置的开始,只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动。
以一个自动配置类HttpEncodingAutoConfiguration(HTTP的编码自动配置)为例子来解释SpringBoot的自动配置之原理:
1). 这个HttpEncodingAutoConfiguration类上面标注了一大堆的注解:
@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final HttpProperties.Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
private static class LocaleCharsetMappingsCustomizer implements
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final HttpProperties.Encoding properties;
LocaleCharsetMappingsCustomizer(HttpProperties.Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
点进去HttpProperties这个类,发现这个HttpProperties类上面标注了@ConfigurationProperties注解
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
/**
* Whether logging of (potentially sensitive) request details at DEBUG and TRACE level
* is allowed.
*/
private boolean logRequestDetails;
/**
* HTTP encoding properties.
*/
private final Encoding encoding = new Encoding();
public boolean isLogRequestDetails() {
return this.logRequestDetails;
}
public void setLogRequestDetails(boolean logRequestDetails) {
this.logRequestDetails = logRequestDetails;
}
public Encoding getEncoding() {
return this.encoding;
}
/**
* Configuration properties for http encoding.
*/
public static class Encoding {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
/**
* Charset of HTTP requests and responses. Added to the "Content-Type" header if
* not set explicitly.
*/
private Charset charset = DEFAULT_CHARSET;
/**
* Whether to force the encoding to the configured charset on HTTP requests and
* responses.
*/
private Boolean force;
/**
* Whether to force the encoding to the configured charset on HTTP requests.
* Defaults to true when "force" has not been specified.
*/
private Boolean forceRequest;
/**
* Whether to force the encoding to the configured charset on HTTP responses.
*/
private Boolean forceResponse;
/**
* Locale in which to encode mapping.
*/
private Map<Locale, Charset> mapping;
public Charset getCharset() {
return this.charset;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public boolean isForce() {
return Boolean.TRUE.equals(this.force);
}
public void setForce(boolean force) {
this.force = force;
}
public boolean isForceRequest() {
return Boolean.TRUE.equals(this.forceRequest);
}
public void setForceRequest(boolean forceRequest) {
this.forceRequest = forceRequest;
}
public boolean isForceResponse() {
return Boolean.TRUE.equals(this.forceResponse);
}
public void setForceResponse(boolean forceResponse) {
this.forceResponse = forceResponse;
}
public Map<Locale, Charset> getMapping() {
return this.mapping;
}
public void setMapping(Map<Locale, Charset> mapping) {
this.mapping = mapping;
}
public boolean shouldForce(Type type) {
Boolean force = (type != Type.REQUEST) ? this.forceResponse
: this.forceRequest;
if (force == null) {
force = this.force;
}
if (force == null) {
force = (type == Type.REQUEST);
}
return force;
}
public enum Type {
REQUEST, RESPONSE
}
}
}
所以说配置文件中该配置什么,我们就按照这个类的Field来,它要配spring.http.encoding这个属性,这个属性里边能配置什么值,就对应HttpEncodingProperties这个类来配置,所有的配置文件中能配置的属性都是在xxx.Properties类中封装着。
所以我们能够配置哪些属性,都是来源于这个功能的properties类。
用好SpringBoot只要把握这几点:
-
SpringBoot启动会加载大量的自动配置类
-
所要做的就是我们需要的功能SpringBoot有没有帮我们写好的自动配置类:
-
如果有就再来看这个自动配置类中到底配置了哪些组件,Springboot自动配置类里边只要我们要用的组件有,我们就不需要再来配置了,但是如果说没有我们所需要的组件,那么我们就需要自己来写一个配置类来把我们相应的组件配置起来。
-
给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,而这些属性我们就可以在配置文件指定这些属性的值
-
xxxxAutoConfigurartion 自动配置类的作用就是给容器中添加组件
xxxxProperties 的作用就是封装配置文件中相关属性