Spring Boot 自动配置原理
前言
使用 Spring Boot 可以高效地开发各种场景,特别是 web 场景下,导入一个 web-starter 依赖就可以完成 web 开发,并且我们并不需要做过多的配置。
简而言之,Spring Boot 的自动配置帮助我们将某些场景的通用配置自动加载到系统中,从而节省时间去完成主要功能。
那么其实现原理是什么呢?
原理
首先查看 Spring Boot 的主启动程序的注解 @SpringBootApplication
我们查看源码可以看到它其实是一个组合注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
【1】@SpringBootConfiguration
【2】@EnableAutoConfiguration
【3】@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}
【1】@SpringBootConfiguration
:标注当前类为一个配置类
【2】@EnableAutoConfiguration
:开启自动配置功能,自动配置实现的重点❗
【3】@ComponentScan
:包扫描,将哪些类作为容器注入到 Spring Boot 中
重点查看 @EnableAutoConfiguration
,查看源码可以看到:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
【1】@AutoConfigurationPackage // 将主启动类所在的包的组件注入
【2】@Import(AutoConfigurationImportSelector.class)// 自动配置的原理
public @interface EnableAutoConfiguration {}
查看 @AutoConfigurationPackage
,查看源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 导入的是这个类,继续查看
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
//===========================================================================
// AutoConfigurationPackage 所导入的类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 这个方法会将主启动类所在包下的组件加入容器,
// 在此处打个端点调试运行,可以看到 getPackageNames() 即为主启动类所在的包下的所有包名
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
❗ 自动配置的重点:@Import(AutoConfigurationImportSelector.class)
,重点查看这个类 AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements ...... {
// 获取自动配置类的集合,这些集合即为要注入的组件,排除掉部分组件后,再封装返回最后要注入的组件
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata){
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 【1】获取一系列自动配置类集合,在这里打个断点,可以查看获取到的自动配置类有哪些。可以查看 22 行的源码
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
// 【2】被设置禁用自动配置的类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
// 【3】封装返回
return new AutoConfigurationEntry(configurations, exclusions);
}
// 获取一系列自动配置类集合
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 获取候选的自动配置类集合,通过工厂类完成,继续查看 SpringFactoriesLoader.loadFactoryNames() 源码,跳转到第 32 行
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<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 调用了 loadSpringFactories,继续查看源码,跳转到第 38 行
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
//===========================================================================
// 为了方便阅读,逻辑有些许改动
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = new LinkedMultiValueMap<>();
// 可以看到,urls 后面被反复操作并将结果封装为 result 返回,可见 urls 是获取候选的自动配置类的重点
// 查看源码,得知 FACTORIES_RESOURCE_LOCATION 的值为 META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
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();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
通过上面的源码查看,我们能够大致地知道 Spring Boot 自动配置的原理为:
- 获取
META-INF/spring.factories
配置文件,从而得到一系列的候选组件 - 排除掉部分候选组件,最后封装返回
而META-INF/spring.factories
在哪呢?一般能够实现自动配置功能的依赖都会有这个文件,而 Spring Boot 就提供了自动配置功能,查看 spring-boot-autoconfigure-2.3.7.RELEASE.jar
,可以看到:
查看文件,果不其然有各种配置类定义:
# 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.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
......
但是,这些自动配置类是否会全部加载,并且其中定义的 Bean 是否也会一并注入到容器中呢?
Spring Boot 是按需加载的,我们打开 AopAutoConfiguration
的源码查看一下
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {}
}
可以看到使用了注解@ConditionalOnXxx
,也就是说,Spring Boot 会根据条件选择是否加载这些配置类、组件到容器中。
因此,当我们需要启用某一块功能时,可以查看是否需要引入相应的 jar 包、启用特定的配置项。
并且有些自动配置类还绑定了配置类XxxProperties
,XxxProperties
又与 Spring Boot配置文件绑定,而自动配置类注入的组件默认就是从 XxxProperties
获取默认值进行设置。如
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// 获取配置信息:webMvcProperties
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
}
}
总结
SpringBoot 主启动类的注解@SpringBootApplication
,其实是组合注解,其组成为:
@SpringBootConfiguration
表明是一个配置类@EnableAutoConfiguration
启用自动配置功能,自动配置的核心@AutoConfigurationPackage
扫描主启动类所在包下的所有组件@Import(AutoConfigurationImportSelector.class)
完成自动配置功能的类的核心组件
@ComponentScan
包扫描配置
SpringBoot 的自动配置流程大致为:
- 启动 SpringBoot 程序,SpringBoot 会扫描 jar 包下的
META_INF/spring.factories
中定义的一系列自动配置类 - 将这些自动配置类扫描进来后,排除掉部分自动配置类
- 按需加载,根据条件
@ConditionalXxx
决定是否加载该自动配置类- 通常也会绑定配置类
XxxProperties.class
- 通常也会绑定配置类
- 生效的配置文件会往容器中注入各种组件
- 由于通过自动配置注入了各种组件,所以某个场景的功能所需的配置就被设置好了,可以直接使用。例如 Spring MVC、DataSource 等等
- 可以自定义配置
- 使用
@Bean
覆盖掉 SpringBoot 底层使用的组件 - 查看底层
XxxProperties
源码使用到的哪些配置项,在配置文件自行配置 - 查看官方文档中各个配置项的说明 Common Application Properties
- 使用
转载自:https://juejin.cn/post/7123479723874713614