1. 程式人生 > >SpringBoot 整合SpringMvc 原理探究(DispatchServlet新增流程)

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物件初始化前,進行行必要的引數配置。

  1. 獲取所有EmbeddedServletContainerCustomizer物件
  2. 呼叫EmbeddedServletContainerCustomizer.customize方法
  3. 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方法。

  1. 在onRefresh方法中,獲取EmbeddedServletContainerFactory物件,因為工程上使用Tomcat,所以這裡就是TomcatEmbeddedServletContainerFactory
  2. 執行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初始化之前或之後完成配置功能或初始化某些依賴功能