这是学透Spring Boot的第14篇文章,更多文章请移步我的专栏:
学透 Spring Boot_postnull咖啡的博客-CSDN博客
目录
没有Spring Boot时的Spring MVC
使用Spring Boot后的Spring MVC
Spring MVC的自动配置解析
明确目标
入口类
Spring容器的启动
Spring容器生命周期hook类
配置类解析类 ConfigurationClassParser
自动配置类列表 AutoConfiguration.imports
Spring MVC的自动配置类WebMvcAutoConfiguration
自动配置的视图解析器
Http消息转换器自动配置类
数据源自动配置类 DataSourceAutoConfiguration
RestClient的自动配置 RestClientAutoConfiguration
嵌入式的web容器配置
DispatcherServlet的自动配置
总结
没有Spring Boot时的Spring MVC
早些年还没有Spring Boot的时候,我们开发一个Spring MVC应用,需要做一大堆的配置,而且和其它的项目比较,这些配置大部分都是大同小异的,我们也可以称之为样板配置。
所以每次新建一个项目,我们通常是复制一个项目,然后复制这个项目的配置,做少量的修改,虽然没有什么太大的问题,但是如果一不小心改错,可能半天都找不到问题。
可以参考之前的一篇文章,里面介绍了没有Spring Boot时完整的手动配置。
学透Spring Boot — [二] Spring 和 Spring Boot的比较-CSDN博客
我们可以大概看看传统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。
@SpringBootApplication
public class JoeLabApplication {public static void main(String[] args) {SpringApplication.run(JoeLabApplication.class, args);}
}
它其实是个组合注解。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
我们重点关注@EnableAutoConfiguration 这个注解,它是一个总开关,开启了自动配置的新世界。
这个注解也是一个组合注解。
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
通过@Import(AutoConfigurationImportSelector.class)这个注解,会触发自动配置类的导入,spring boot会用这个类去完成自动配置的功能。
Spring容器的启动
这次,我们先看看Spring Boot的启动,来分析自动配置是如何生效。
public class SpringApplication {public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);}
}
SpringBoot的run方法,会创建并刷新Spring容器。
public ConfigurableApplicationContext run(String... args) {context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context); // 刷新 Spring 容器,加载各种 BeanafterRefresh(context, applicationArguments);startup.started();
}
关注:refreshContext(context); // 刷新 Spring 容器,加载各种 Bean
接着刷新容器
刷新容器的关键过程包括 Bean 的加载与初始化。refreshContext 方法会启动各类 Bean 的生命周期,调用 invokeBeanFactoryPostProcessors 来执行 BeanFactory 后处理器。
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就提供了一个钩子类。
它是生命周期类,所以启动过程中自动被找到并执行。
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanRegistrationAotProcessor, BeanFactoryInitializationAotProcessor, PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
}
这个类会去处理配置类 也就是加了@Configuration的类
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");parser.parse(candidates);parser.validate();}
配置类解析类 ConfigurationClassParser
接下来,它把任务交给了解析器——ConfigurationClassParser
最终这个解析工具类,通过一长串的调用链,最终到了另一个工具类ImportCandidates
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);}
}
我们重点看这一行代码
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的组件。
自动配置的视图解析器
其中,就包含默认的视图解析器。
@Bean@ConditionalOnMissingBeanpublic 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消息转换器自动配置类
@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 {
它导入了三种解析器
@Import({ JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class,JsonbHttpMessageConvertersConfiguration.class })
其中Jackson的是
@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
@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
@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@ConditionalOnMissingBeanRestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {return restClientBuilderConfigurer.configure(RestClient.builder());}
嵌入式的web容器配置
@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 {@Beanpublic TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,ServerProperties serverProperties) {return new TomcatWebServerFactoryCustomizer(environment, serverProperties);}@Bean@ConditionalOnThreading(Threading.VIRTUAL)TomcatVirtualThreadsWebServerFactoryCustomizer tomcatVirtualThreadsProtocolHandlerCustomizer() {return new TomcatVirtualThreadsWebServerFactoryCustomizer();}}
DispatcherServlet的自动配置
@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