SpringBoot 整合SpringMvc 原理探究(DispatchServlet新增流程)
通過SpringBoot整合各個框架是越來越方便了,整合SpringMVC只需要新增對應的starer依賴即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
而且還配備了Tomcat的starter
<dependency>
<groupId>org.springframework.boot</groupId >
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
這樣,只需要根據自身需求,設定配置檔案。啟動web伺服器只需要執行java application就可以了,不再需要部署到tomcat服務了。
之前一直很好奇,使用SpringMVC時需要在web.xml上配置DispatcherServlet。而整合了SpringBoot後為什麼就不需要配置了,下面就進行完整的分析。
看著累?可以直接看步驟7,核心分析。
1、尋找入口,找到WebServlet自動配置類:EmbeddedServletContainerAutoConfiguration
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration{
...省略程式碼
}
SpringBoot 自動配置功能類都以AutoConfiguration結尾
2、注入需要的Bean
從類上的註解可以看出,匯入了BeanPostProcessorsRegistrar,來新增EmbeddedServletContainerCustomizerBeanPostProcessor。首先會檢視工程是否有自定的EmbeddedServletContainerCustomizerBeanPostProcessor,如果沒有,則注入預設的EmbeddedServletContainerCustomizerBeanPostProcessor。程式碼如下:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(
EmbeddedServletContainerCustomizerBeanPostProcessor.class, true,
false))) {
registry.registerBeanDefinition( "embeddedServletContainerCustomizerBeanPostProcessor",
new RootBeanDefinition(
EmbeddedServletContainerCustomizerBeanPostProcessor.class));
}
if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(
ErrorPageRegistrarBeanPostProcessor.class, true, false))) {
registry.registerBeanDefinition("errorPageRegistrarBeanPostProcessor",
new RootBeanDefinition(
ErrorPageRegistrarBeanPostProcessor.class));
}
}
實現ImportBeanDefinitionRegistrar介面,實現注入需要的Bean到Spring容器中,Mybatis(MapperScannerRegistrar)也是通過此介面來完成Mapper類的定義。
3、步驟2注入了bean:EmbeddedServletContainerCustomizerBeanPostProcessor,該類實現了在ConfigurableEmbeddedServletContainer物件初始化前,進行行必要的引數配置。
- 獲取所有EmbeddedServletContainerCustomizer物件
- 呼叫EmbeddedServletContainerCustomizer.customize方法
- EmbeddedServletContainerCustomizer實現類根據自身需求設定WebServlet容器引數(如:埠號、連線數等等)
核心程式碼如下:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {
if (bean instanceof ConfigurableEmbeddedServletContainer) { postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
ConfigurableEmbeddedServletContainer:是Web容器的介面,預設注入的有
BeanPostProcessor : 是Spring容器的回撥介面,在所有Bean初始化之前和之後分別回撥此介面的postProcessBeforeInitialization,postProcessAfterInitialization方法。這樣就可以根據需求在Bean初始化前後配置設定需要的功能。
通過步驟1-3完成了Web容器啟動前的引數配置功能。
4、EmbeddedWebApplicationContext入場
Spring容器配置載入完成後,會回撥EmbeddedWebApplicationContext.refresh方法。EmbeddedWebApplicationContext在執行refresh方法中,呼叫了onRefresh方法進行ServletContainer配置。程式碼如下:
@Override
public final void refresh() throws BeansException, IllegalStateException {
...省略
super.refresh();
...省略
}
@Override
protected void onRefresh() {
...省略
//建立ServletContainer
createEmbeddedServletContainer();
...省略
}
EmbeddedWebApplicationContext實現了介面ConfigurableApplicationContext,Spring容器配置載入完成後會回撥所有的ConfigurableApplicationContext物件的refresh方法。
- 在onRefresh方法中,獲取EmbeddedServletContainerFactory物件,因為工程上使用Tomcat,所以這裡就是TomcatEmbeddedServletContainerFactory
- 執行EmbeddedServletContainerFactory.getEmbeddedServletContainer方法
5、TomcatEmbeddedServletContainerFactory.getEmbeddedServletContainer這裡是Tomcat容器核心功能完的地方。主要完成了對Tomcat配置(不是這篇重點,省略程式碼),在configureContext方法新增Tomcat容器啟動回撥介面(重點)。
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
// Should be true
((TomcatEmbeddedContext) context).setStarter(starter);
}
...省略
}
ServletContainerInitializer是Tomcat容器啟動的一個回撥介面。
在Tomcat啟動前,SpringBoot通過TomcatStarter完成Servlet,Filter等Web元件的組注入
6、TomcatStarter,在Tomcat啟動後回撥onStartup。
7、EmbeddedWebApplicationContext在onStartup回撥中完成SpringMvc功能注入
7.1、在selfInitialize方法中獲取到所有ServletContextInitializer物件,並呼叫其onStartup方法,
7.2、ServletContextInitializer實現類如下:
7.3、通過上圖就很清楚的說明了Servlet,Filter等Web元件實現類
7.4、在ServletRegistrationBean向ServletContainer新增Servlet
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
Assert.notNull(this.servlet, "Servlet must not be null");
String name = getServletName();
if (!isEnabled()) {
logger.info("Servlet " + name + " was not registered (disabled)");
return;
}
logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);
Dynamic added = servletContext.addServlet(name, this.servlet);
if (added == null) {
logger.info("Servlet " + name + " was not registered "
+ "(possibly already registered?)");
return;
}
configure(added);
}
7.5、這裡也就解釋了SpringBoot官方文件70.1節上為什麼是通過RegistrationBean新增Servlet與Filter的原因了。
7.6、DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration此處新增SpringMVC核心功能類DispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
以上只是將重要點抽出來說明,貼上全部原始碼也是無意義的。要理解其中過程還需要自行檢視原始碼。
通過以上步驟分析了SpringBoot整合SpringMVC和Tomcat功能簡要步驟。其實只要找到了入口,即可Debug一步一步的走下去,來檢視內部實現。
總結
通過以上分析和Mybatis功能分析,發現滿滿的都是套路。在SpringBoot上實現自定義Starter功能應該都是如下套路:
1、在自定義的XXAutoConfiguration上Import一個ImportBeanDefinitionRegistrar來注入指定Bean
2、新增自定義的BeanPostProcessor在Bean初始化之前或之後完成配置功能或初始化某些依賴功能