这是学透Spring Boot的第14篇文章,更多文章请移步我的专栏:
学透 Spring Boot_postnull咖啡的博客-CSDN博客
目录
[没有Spring Boot时的Spring MVC](#没有Spring Boot时的Spring MVC)
[使用Spring Boot后的Spring MVC](#使用Spring Boot后的Spring MVC)
[Spring MVC的自动配置解析](#Spring MVC的自动配置解析)
[配置类解析类 ConfigurationClassParser](#配置类解析类 ConfigurationClassParser)
[自动配置类列表 AutoConfiguration.imports](#自动配置类列表 AutoConfiguration.imports)
[Spring MVC的自动配置类WebMvcAutoConfiguration](#Spring MVC的自动配置类WebMvcAutoConfiguration)
[数据源自动配置类 DataSourceAutoConfiguration](#数据源自动配置类 DataSourceAutoConfiguration)
[RestClient的自动配置 RestClientAutoConfiguration](#RestClient的自动配置 RestClientAutoConfiguration)
没有Spring Boot时的Spring MVC
早些年还没有Spring Boot的时候,我们开发一个Spring MVC应用,需要做一大堆的配置,而且和其它的项目比较,这些配置大部分都是大同小异的,我们也可以称之为样板配置。
所以每次新建一个项目,我们通常是复制一个项目,然后复制这个项目的配置,做少量的修改,虽然没有什么太大的问题,但是如果一不小心改错,可能半天都找不到问题。
可以参考之前的一篇文章,里面介绍了没有Spring Boot时完整的手动配置。
我们可以大概看看传统Spring MVC项目的配置
web.xml 中配置DispatchServlet

在 servlet-context.xml 中配置 Spring MVC 相关组件

这两份配置,非常冗余,因为绝大部分项目都大同小异。
最后再实现控制器

使用Spring Boot后的Spring MVC
如果使用Spring Boot,事情变得非常简单。
我们只要在我们的应用启动类添加一个注解 @SpringBootApplication
然后,所有的事情,SpringBoot都会自动帮我们完成。

简直是单车 到 摩托车的飞跃。

Spring MVC的自动配置解析
下面我们一步步来研究,Spring Boot是如何做到自动配置MVC的。
明确目标
自动配置的结果,就是把手动显示的配置,变成自动的配置。
比如servlet-context.xml中配置的视图解析器

Spring Boot 它会通过@Bean声明的方式,帮我们创建一个视图解析器的Bean

我们今天的任务,就是要搞清楚,Spring Boot在哪里以及什么时候,帮我创建的这个Bean。
入口类
首先我们的入口类,使用了一个注解@SpringBootApplication。
java
@SpringBootApplication
public class JoeLabApplication {
public static void main(String[] args) {
SpringApplication.run(JoeLabApplication.class, args);
}
}
它其实是个组合注解。
java
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
我们重点关注@EnableAutoConfiguration 这个注解,它是一个总开关,开启了自动配置的新世界。
这个注解也是一个组合注解。
java
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
通过@Import(AutoConfigurationImportSelector.class)这个注解,会触发自动配置类的导入,spring boot会用这个类去完成自动配置的功能。
Spring容器的启动
这次,我们先看看Spring Boot的启动,来分析自动配置是如何生效。
java
public class SpringApplication {
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
}
SpringBoot的run方法,会创建并刷新Spring容器。
java
public ConfigurableApplicationContext run(String... args) {
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context); // 刷新 Spring 容器,加载各种 Bean
afterRefresh(context, applicationArguments);
startup.started();
}
关注:refreshContext(context); // 刷新 Spring 容器,加载各种 Bean
接着刷新容器
刷新容器的关键过程包括 Bean 的加载与初始化。refreshContext 方法会启动各类 Bean 的生命周期,调用 invokeBeanFactoryPostProcessors 来执行 BeanFactory 后处理器。
java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors());
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean("loadTimeWeaver")) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
重点关注:invokeBeanFactoryPostProcessors
我们理解成Spring提供的生命周期钩子就行。通过这些钩子,我们可以在Spring启动过程中,做一些特殊的工作。比如自动化配置各种bean。
Spring容器生命周期hook类
其中Spring就提供了一个钩子类。
它是生命周期类,所以启动过程中自动被找到并执行。
java
public class ConfigurationClassPostProcessor
implements BeanDefinitionRegistryPostProcessor,
BeanRegistrationAotProcessor,
BeanFactoryInitializationAotProcessor,
PriorityOrdered,
ResourceLoaderAware,
ApplicationStartupAware,
BeanClassLoaderAware,
EnvironmentAware {
}
这个类会去处理配置类 也就是加了@Configuration的类
java
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();
}
配置类解析类 ConfigurationClassParser
接下来,它把任务交给了解析器------ConfigurationClassParser
最终这个解析工具类,通过一长串的调用链,最终到了另一个工具类ImportCandidates
java
public final class ImportCandidates implements Iterable<String> {
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = String.format(LOCATION, annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> importCandidates = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}
}
我们重点看这一行代码
java
String location = String.format(LOCATION, annotation.getName());
其中:private static final String LOCATION = "META-INF/spring/%s.imports";
所以location是:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
这个文件,在我们的自动配置模块下

自动配置类列表 AutoConfiguration.imports
我们把这个文件的内容罗列出来

是不是很多名字都似曾相识呢?
是的,这就是SpringBoot提供的自动配置类,对各种常用的组件,都提供了自动配置类。
SpringBoot在启动Spring容器的过程中,会定位到这个文件,然后逐个尝试去加载配置类。
Spring MVC的自动配置类WebMvcAutoConfiguration
我们先重点关注其中一个WebMvcAutoConfiguration
这个类提就是Spring MVC的自动配置类。

这个配置类会被找到,但是要不要加载,得看条件。条件配置就是它上面的注解。
我们逐条解析:
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class }
DispatcherServlet配置完后,才会配置Spring MVC。
说得通,就像我们先配置web.xml中的DispatcherServlet,再配置Spring mvc的配置servlet.xml
@ConditionalOnWebApplication(type = Type.SERVLET)必须是Spring MVC(Servlet)的web才会加载。
如果是WebFlux的web,就不会自动配置MVC。
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })Classpath下有这个两个类。
表示我们引入了Spring mvc的依赖。
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)只有当Spring容器没有WebMvcConfigurationSupport这个bean时,才会配置MVC。
因为这个Bean是用来给我们自定义的,如果我们不想用自动配置,而是想覆盖默认配置,我们就需要继承这个类。这样,SpringBoot就以我们配置的为主,而忽略自动配置。
这些条件,我们都满足,所以Spring Boot开始用这个类进行自动配置MVC。
这个配置类中定义了很多Bean,这些bean就是MVC的组件。
自动配置的视图解析器
其中,就包含默认的视图解析器。
java
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}
还记得我们在传统Spring MVC项目手动的配置吗?是的,我们做到了,通过自动创建Bean的方式,成功完成了视图解析器的配置。

我们在看看其它的自动配置类。
都定义在org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件下。
Http消息转换器自动配置类
java
@AutoConfiguration(
after = { GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class })
@ConditionalOnClass(HttpMessageConverter.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@Import({ JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class,
JsonbHttpMessageConvertersConfiguration.class })
@ImportRuntimeHints(HttpMessageConvertersAutoConfigurationRuntimeHints.class)
public class HttpMessageConvertersAutoConfiguration {
它导入了三种解析器
java
@Import({
JacksonHttpMessageConvertersConfiguration.class,
GsonHttpMessageConvertersConfiguration.class,
JsonbHttpMessageConvertersConfiguration.class })
其中Jackson的是
java
@Bean
@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class,
ignoredType = {
"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper);
}
数据源自动配置类 DataSourceAutoConfiguration
java
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
@Bean
@ConditionalOnMissingBean(JdbcConnectionDetails.class)
PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
return new PropertiesJdbcConnectionDetails(properties);
}
}
RestClient的自动配置 RestClientAutoConfiguration
java
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@ConditionalOnMissingBean
RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
return restClientBuilderConfigurer.configure(RestClient.builder());
}
嵌入式的web容器配置
java
@AutoConfiguration
@ConditionalOnNotWarDeployment
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
@Bean
@ConditionalOnThreading(Threading.VIRTUAL)
TomcatVirtualThreadsWebServerFactoryCustomizer tomcatVirtualThreadsProtocolHandlerCustomizer() {
return new TomcatVirtualThreadsWebServerFactoryCustomizer();
}
}
DispatcherServlet的自动配置
java
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
总结
我们这篇文章,从另一个角度------Spring容器的启动过程,结合SpringBoot提供的注解,理解了Spring Boot的自动配置原理。
最终定义到自动配置类的列表文件:
org.springframework.boot.autoconfigure.AutoConfiguration.imports