這是學透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