spring boot 自动配置
14 Apr 2020Spring Boot内部对大量的第三方库或者Spring内部库进行了默认的配置,这些配置是否生效,取决于我们是否引入了对应库所需的依赖,而且通过全局配置文件可以对默认配置进行修改。自动配置的目的就是让第三方jar包里面的类很方便的添加到 IOC
容器中去,大大简化了项目的配置过程。
Spring Boot项目是怎么找到这些可以自动配置的第三方jar包中的类的?
@SpringBootApplication
public class AutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(AutoconfigApplication.class, args);
}
}
这是Spring Boot项目的启动入口,也叫主配置类。@SpringBootApplication 是个组合注解,内部如下:
@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
: 其实里面是个@Configuration注解,表明Spring Boot项目的入口类也是个配置类。@ComponentScan
:被这个注解的类所在包以及子包下的所有”组件“都会被扫描,并将其bean添加到spring 容器里面。@EnableAutoConfiguration
:字面意思:启动自动配置,很显然用该是自动配置的入口了。进入~~
Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage
:它的功能简单说就是将应用的root package给注册到Spring容器中,供后续使用。@Import(AutoConfigurationImportSelector.class)
:字面意思:导入自动配置选择器。
看了好多博客,里面描述的都是@Import注解导入
AutoConfigurationImportSelector
后,调用了它里面的selectImports()
方法等等。我在这个方法里面打断点,之后启动debug,程序并没有在这个方法中停下来而是正常启动,所以应该是没有调用这个方法。我用的Spring Boot 2.2.6,但那么多人这么写,总不是空穴来风吧,这个之后在仔细研究吧。
但能确定的是自动配置调用了 AutoConfigurationImportSelector
类中的 getAutoConfigurationEntry
方法。
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有候选的配置。
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 将List放入Set再放入List,去掉重复的类名。
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);
}
获取类路径下所有jar包中的需要自动配置的类是通过 getCandidateConfigurations()
方法实现的。具体内容:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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
方法去加载配置。最后以 List<String>
的形式将加载到的配置返回。
SpringFactoriesLoader.loadFactoryNames方法:里面也很简单,如下所示,里面定义了factoryTypeName
并且调用了 loadSpringFactories(classLoader)
方法后将结果返回,根据 getOrDefault
方法,我们应该能猜出loadSpringFactories(classLoader)
的返回值是个map类型的,再结果是以 List<String>
形式返回,就能确定loadSpringFactories(classLoader)
的返回值形式是:Map<String, List<String>>
。(这段有点瞎扯,我分析这个干毛线啊,瞅一眼不不就行了吗)
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName,Collections.emptyList());
}
其实在Spring Boot项目中,可以自动配置的类的类名都存放在jar包中的META-INF/spring.factories
文件中,loadSpringFactories方法的功能就是从META-INF/spring.factories
文件中将所有可以自动配置的类的类名给读出来,并存放到Map<String, List<String>>
类型的对象中,最后返回,就干了这么一件事情。getOrDefault(factoryTypeName, Collections.emptyList())
的作用就是,从全部的可以自动配置的类中,找出我想要的那一组。factoryTypeName
的值就是spring.factories文件中等号左边的值,比如:ApplicationContextInitializer、ApplicationListener等等。
这个是spring-boot-autoconfigure-2.2.6.RELEASE.jar下的spring.factories文件内容:(当然别的jar包中也有spring.factories文件)。
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
.........
loadSpringFactories方法:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先从缓存中加载这些可以自动配置的类名,如果能加载到就直接返回,如果不能再从文件中加载。
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//拿到类路径下的所有jar包中的spring.factories的路径,并存放到urls变量中。
// FACTORIES_RESOURCE_LOCATION = META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
// 拿出一个spring.factories的路径,扫描里面可以自动配置的类的类名。
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
/**
* 将每一个spring.factories中的要自动跑配置的类名以key-value的形式封装成properties对象。
* key就是等号左边的,value就是等号右边。
*/
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// 将可以自动配置的类的类名存放到result 中。
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
// 将可以自动配置的类的类名加载到缓存中。
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
在Spring Boot项目启动时可以自动配置的类就这样被全部扫描到了,接下来就是按照类的路径进行加载,创建bean并将其加入Spring容器中。
类找到了,自动配置是怎么实现的呢?
再来看下spring-boot-autoconfigure-2.2.6.RELEASE.jar下的spring.factories文件中 EnableAutoConfiguration
部分。下图中截取了其中一部分。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
.........
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,
.........
可以看到,@EnableAutoConfiguration注解的自动配置类都是以 XXXConfiguration
结尾的,
我们以 ServletWebServerFactoryAutoConfiguration
为例看看里面到底是什么。
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
@Configuration
:表明ServletWebServerFactoryAutoConfiguration
是个javaConfig类型的配置类,配置类的作用是给Spring 容器中添加bean的。@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
: 表示这个类的配置顺序是高优先级。@ConditionalOnClass(ServletRequest.class)
:表示当ServletRequest.class在类路径下存在时,这个配置类才生效。-
@ConditionalOnWebApplication(type = Type.SERVLET)
:表示当前的Spring Boot应用程序是web应用程序的时候,这个配置类才生效。 -
@EnableConfigurationProperties(ServerProperties.class)
: 这个注解比较重要了,自动配置就是从这开始的。EnableConfigurationProperties 字面意思:启动配置属性,它的实际作用是将ServerProperties.class
生成bean,并添加到Spring 容器中。 @Import()
:这个注解就是导入一些其他类的bean。
在这些注解中,3和4是条件注解,而且只有当这两个条件同事满足时,ServletWebServerFactoryAutoConfiguration
配置类才生效。
那接下来还有个问题:@EnableConfigurationProperties将ServletRequest.class的bean添加到了Spring 容器中,那这个bean的属性的值是什么呢?
先来看下 ServerProperties.class
类,也只截取了一小部分。
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind.
*/
private InetAddress address;
@ConfigurationProperties
注解的功能是将全局配置文件中的属性和ServerProperties类中的属性绑定起来,说白了就是用配置文件中的属性更新ServerProperties类中对应的属性的默认值。prefix = "server"
指的是在全局配置中属性的前缀是什么。
在全局配置文件 application.properties
中,我们常会看到一个配置:
server.port = 8080
这个配置是用来修改服务的HTTP端口的,当 ServerProperties
生成对应的bean时,就会拿 server.port
的值更新 ServerProperties
的 port
属性的默认值。
ServerProperties
每个属性都有默认的值,当全局配置文件中没有配置对应属性时,ServerProperties
就会使用默认值来出生成bean。
总结
ServletWebServerFactoryAutoConfiguration的自动配置过程总结一下:
目的:
自动配置ServletWebServerFactoryAutoConfiguration的目的是在Spring Boot项目启动时,将server相关的类生成对应的bean添加到Spring 容器中。
执行过程:
ServletWebServerFactoryAutoConfiguration类的路径在在spring.factories中被扫描到,在将添加server相关bean时,先是确定ServerProperties 中属性的值,通过@ConfigurationProperties注解从全局配置值中读取,如果能读到,则就用全局配置的中的值,如果读不到,就用默认值。@EnableConfigurationProperties注解将确定值的ServerProperties类生成bean并且添加到Spring 容器中去。接着是ServletWebServerFactoryAutoConfiguration类中的方法使用ServerProperties bean 去生成其他的 server bean,并添加到Spring 容器。这样跟server相关的组件在项目启动时就被加入到了spring容器中。
EnableAutoConfiguration下的其他的XXXConfiguration的自动配置也是这样的流程。