主頁 > 後端開發 > SpringBoot中Tomcat和SpringMVC整合原始碼分析

SpringBoot中Tomcat和SpringMVC整合原始碼分析

2022-07-25 08:40:21 後端開發

概述

? SpringBoot中集成官方的第三方組件是通過在POM檔案中添加組件的starter的Maven依賴來完成的,添加相關的Maven依賴之后,會引入具體的jar包,在SpringBoot啟動的時候會根據默認自動裝配的配置類的注入條件判斷是否注入該自動配置類到Spring容器中,自動配置類中會創建具體的第三方組件需要的類 ,Tomcat和SpringMVC都是通過這樣的方式進行集成的,SpringBoot出現之前SpringMVC專案是直接部署在Tomcat服務器中的,Tomcat是一個符合Servlet標準的Web服務器,Tomcat單獨作為一個可安裝軟體,這種方式下Tomcat是一個完整獨立的web服務器,SpringMVC專案不能脫離Web服務器直接運行,需要先部署在Tomcat服務器的目錄中才能運行,SpringBoot出現之后改變了這種方式,我們可以直接運行SpringBoot專案,因為SpringBoot中可以內嵌tomcat服務器,而tomcat也是java開發的,在啟動SpringBoot專案的時候直接創建tomcat服務并且把SpringMVC的專案部署在tomcat服務中,

一、自動裝配原理

? 在SpringBoot自動裝配的程中,會加載/META-INF/factories檔案中的以EnableAutoConfiguration為Key的配置類的字串陣列,在該類中會根據具體引入的服務器類進行創建具體的服務器物件,這些字串陣列是否真正加載進Spring容器,需要經過兩方面的判斷,

? 1.根據META-INF/spring-autoconfigure-metadata.properties檔案中配置的規則判斷是否需要過濾,

? 2.在org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass中會根據自動裝配類的條件注解來判斷是否進行自動加載,如果條件注解全部校驗成功才會加載該配置類,

兩種情況的底層校驗邏輯都是一樣的,都是通過呼叫OnWebApplicationCondition、OnClassCondition、OnBeanCondition的match方法進行判斷,但是在processConfigurationClass方式的檢查型別會更多,比如ConditionalOnMissingBean 等條件的檢查,

條件注解 注解備注
OnWebApplicationCondition 判斷是否符合服務器型別
OnClassCondition 判斷專案中是否存在配置的類
OnBeanCondition 判斷Spring容器中是否注入配置的類

二、內嵌式Tomcat注入

2.1自動注入配置類分析

1.ServletWebServerFactoryAutoConfiguration 配置類分析

? 1.META-INF/factories檔案中以EnableAutoConfiguration為Key的配置類的字串陣列,其中 ServletWebServerFactoryAutoConfiguration 為創建Web服務器的自動配置類,根據META-INF/spring-autoconfigure-metadata.properties檔案中配置的規則判斷是否需要過濾,該檔案關于ServletWebServerFactoryAutoConfiguration的配置描述如下,

org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.ConditionalOnClass=javax.servlet.ServletRequest
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.ConditionalOnWebApplication=SERVLET
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.AutoConfigureOrder=-2147483648

此配置說明需要在當前的專案中有javax.servlet.ServletRequest類并且WebApplication是SERVLET容器,才不會過濾,

  1. 根據自動裝配類的條件注解來判斷是否進行自動加載,ServletWebServerFactoryAutoConfiguration 配置類的條件注解如下,
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 判斷是否有 ServletRequest 類
@ConditionalOnClass(ServletRequest.class)
// 判斷 WebApplication 是 SERVLET 容器
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)

此配置說明需要在當前的專案中有javax.servlet.ServletRequest類并且WebApplication是SERVLET容器,才會進行ServletWebServerFactoryAutoConfiguration 類的加載,

2.2注入邏輯具體分析

  1. tomcat的starter依賴配置代碼如下,
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.2.5.RELEASE</version>
    <scope>compile</scope>
</dependency>
  1. 添加Maven依賴配置后,會引入tomcat-embed-corejar包,其中有ServletRequest介面,代碼截圖如下,

SpringBoot的在創建服務器型別的時候是Servlet的,在創建SpringApplication的run方法中會呼叫createApplicationContext方法創建具體的AnnotationConfigServletWebServerApplicationContext類,該類繼承了org.springframework.web.context.support.GenericWebApplicationContext類,OnWebApplicationCondition條件注解中就是通過判斷是否有GenericWebApplicationContext類來檢查是否是SERVLET型別的,

  1. 所以添加完tomcat的starter依賴配置后,ServletWebServerFactoryAutoConfiguration所有的條件注解都會匹配成功 ,即會自動加載 ServletWebServerFactoryAutoConfiguration 自動裝配類,該類會通過@Import注解匯入3個內嵌服務器的實作類,這3個實作類的服務器都是Servlet標準的,代碼如下,
// 匯入具體服務器類
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
         ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
         ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
         ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })

三個Embeddedxxx表示相應的服務器實作類,每個服務器類都有自己的條件注解實作不同的加載邏輯,如果這里注入了多個web服務器,在服務器啟動的時候會報錯,EmbeddedTomcat類的代碼如下,

@Configuration(proxyBeanMethods = false)
#當前工程下需要有Servlet、Tomcat UpgradeProtocol類
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
#當前的Spring容器類不能有ServletWebServerFactory型別的Bean,搜索的是當前容器,
@ConditionalOnMissingBean(value = https://www.cnblogs.com/yuanbeier/archive/2022/07/24/ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
    @Bean
    TomcatServletWebServerFactory tomcatServletWebServerFactory(
        ObjectProvider connectorCustomizers,
        ObjectProvider contextCustomizers,
        ObjectProvider> protocolHandlerCustomizers) {
        // 創建 TomcatServletWebServerFactory ,是產生TomcatServletWebServer的工廠類
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.getTomcatConnectorCustomizers()
            .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getTomcatContextCustomizers()
            .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getTomcatProtocolHandlerCustomizers()
            .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
        return factory;
    }
}

從以上代碼看出,EmbeddedTomcat配置類能加載注入的條件是當前工程下需要有Servlet、Tomcat、 UpgradeProtocol3個類并且當前Spring容器沒有注入ServletWebServerFactory型別的Bean,而這個三個類都在tomcat-embed-core.jar包中,并且Spring容器中之前沒有注入ServletWebServerFactory型別的Bean,所會自動加載EmbeddedTomcat類,EmbeddedTomcat用來注入TomcatServletWebServerFactory到Spring容器,TomcatServletWebServerFactory的實作了ServletWebServerFactory介面,TomcatServletWebServerFactory可以創建具體的TomcatWebServer類,在SpringBoot啟動的時候會從Spring容器中獲取ServletWebServerFactory型別的Bean,至此Tomcat的自動裝置決議完了,

三、SpringMVC注入

? SpringMVC中最重要的組件是DispatchServlet,SpringMVC要在Servlet型別的Web服務器運行,就要把Servlet添加到Web容器中,用Servlet來處理請求,回想一下以前的SpringMVC,我們需要在web.xml組態檔中添加Servlet的配置,會默認把SpringMVC的DispatchServlet配置到Servlet配置節點中,web.xm配置節點代碼如下,

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
            </init-param>
            <!--Web服務器一旦啟動,Servlet就會實體化創建物件,然后初始化(預備創建物件)-->
            <load-on-startup>1</load-on-startup>
</servlet>

在SpringBoot中我們使用的是內嵌式的Tomcat,那tomcat和SpringMVC的DispatchServlet是怎么關聯起來的?一起往下看,META-INF/factories檔案中的以EnableAutoConfiguration為Key的配置類的字串陣列,其中 DispatcherServletAutoConfiguration 和 WebMvcAutoConfiguration 是SpringMVC中最為重要的兩個自動配置類,DispatcherServletAutoConfiguration 用來注冊 DispatcherServlet到 Spring容器,WebMvcAutoConfiguration 用來注入MVC的相關組件到Spring容器,

3.1自動注入配置類分析

1.DispatcherServletAutoConfiguration 配置類分析

? 1.1 根據META-INF/spring-autoconfigure-metadata.properties檔案中配置的規則判斷是否需要過濾,該檔案關于DispatcherServletAutoConfiguration 的配置描述如下,

#WebApplication是SERVLET容器
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.ConditionalOnWebApplication=SERVLET
#工程中需要引入 DispatcherServlet 類
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
#容器注入在 ServletWebServerFactoryAutoConfiguration(服務器自動配置類) 類之后,
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureOrder=-2147483648

DispatcherServletAutoConfiguration 的邏輯為:需要在當前的專案中有DispatcherServlet類并且WebApplication是SERVLET容器,才不會過濾掉,

? 1.2 根據自動裝配類的條件注解來判斷是否進行自動加載,DispatcherServletAutoConfiguration 配置類的條件注解如下,

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)

此邏輯需要在當前的專案中有DispatcherServlet類并且WebApplication是SERVLET容器,并且注入在 DispatcherServletAutoConfiguration 類之后,才會進行DispatcherServletAutoConfiguration 類的加載,在DispatcherServletAutoConfiguration 中有兩個非常重要的內部類,DispatcherServletConfiguration 和DispatcherServletRegistrationConfiguration ,這兩個內部類也會根據條件注解加載來決定是否加載進Spring容器中 ,DispatcherServletConfiguration 主要用來注入 DispatcherServlet ;DispatcherServletRegistrationConfiguration 主要用來注入 DispatcherServletRegistrationBean,DispatcherServletRegistrationBean 用來包裝 DispatcherServlet 類,它實作了ServletContextInitializer介面,用來注冊Servlet物件到tomcat中的ServletContext物件,

2.DispatcherServletConfiguration 配置類分析

? DispatcherServletConfiguration 中有個DispatcherServlet型別的@Bean物件,會把DispatcherServlet注入到Spring容器中,DispatcherServletConfiguration 代碼如下,

@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
        return dispatcherServlet;
    }
}

以上條件注解代碼可知,將BeanName為 dispatcherServlet ,型別為DispatcherServlet 的Bean注入到容器中的條件為:先查找是否已經容器中是否存在名稱 dispatcherServlet的Bean或者型別DispatcherServlet 的Bean,如果都不存并且當前工程中有ServletRegistration類則將DispatcherServletConfiguration注入到Spring容器中,并且還會注入DispatcherServlet類,

  1. DispatcherServletRegistrationConfiguration 配置類分析

? DispatcherServletRegistrationConfiguration 主要用來注入 DispatcherServletRegistrationBean,DispatcherServletRegistrationBean 用來包裝 DispatcherServlet 類,它實作了ServletContextInitializer介面,SpringBoot用DispatcherServletRegistrationBean 來注冊Servlet物件到Tomcat服務器中的ServletContext物件,在后面Tomcat啟動的時候會講具體實作,DispatcherServletRegistrationConfiguration代碼如下,

@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = https://www.cnblogs.com/yuanbeier/archive/2022/07/24/DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                                                                           WebMvcProperties webMvcProperties, ObjectProvider multipartConfig) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                                                                                               webMvcProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }
}

以上條件注解代碼可知,將BeanName為 dispatcherServletRegistration,型別為DispatcherServletRegistrationBean的Bean注入到容器中的條件為:先查找是否已經容器中是否存在名稱 dispatcherServletRegistration的Bean或者型別DispatcherServletRegistrationBean的Bean,如果都不存并且當前工程中有ServletRegistration類則將DispatcherServletRegistrationConfiguration注入到Spring容器中,并且還會注入DispatcherServletRegistrationBean類,

  1. WebMvcAutoConfiguration

? 4. 1 根據META-INF/spring-autoconfigure-metadata.properties檔案中配置的規則判斷是否需要過濾,該檔案關于 WebMvcAutoConfiguration 的配置描述如下,

#工程中需要引入Servlet、WebMvcConfigurer、 DispatcherServlet類 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.ConditionalOnClass=javax.servlet.Servlet,org.springframework.web.servlet.config.annotation.WebMvcConfigurer,org.springframework.web.servlet.DispatcherServlet
# 容器注入在 DispatcherServletAutoConfiguration、TaskExecutionAutoConfiguration、ValidationAutoConfiguration之
# 后
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638

WebMvcAutoConfiguration的邏輯為:需要在當前的專案中有Servlet、WebMvcConfigurer、DispatcherServlet3個類,才不會過濾掉,

? 4.2 根據自動裝配類的條件注解來判斷是否進行自動加載,WebMvcAutoConfiguration配置類的條件注解如下,

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })

此邏輯需要當前的應用程式為SERVLET型別,當前的專案中有Servlet、DispatcherServlet、WebMvcConfigurer3個類、WebMvcConfigurationSupport沒有被注入到Spring容器、在 DispatcherServletAutoConfiguration 、TaskExecutionAutoConfiguration、ValidationAutoConfiguration 類注入到Spring容器之后,才會進行WebMvcAutoConfiguration類的注入,在該中有個內部類WebMvcAutoConfigurationAdapter ,主要用來注入 SpringMVC的組件,比如ViewResolver、LocaleResolver等,

3.2 注入邏輯具體分析

  1. springmvc的starter依賴配置代碼如下
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.4.RELEASE</version>
    <scope>compile</scope>
</dependency>
  1. 添加Maven依賴配置后,會引入spring-webmvc.jar包,其中有DispatcherServlet類、WebMvcConfigurer介面,而springmvc的自動配置類在ServletWebServerFactoryAutoConfiguration配置類加載之后進行加載,所以肯定會有Servlet的存在,

  2. 所以添加完springmvc的Maven依賴配置后,會自動加載 DispatcherServletAutoConfiguration類和WebMvcAutoConfiguration類以及他們配置類中其他的配置類,至此SpringMVC的自動裝置決議完了,

四、Tomcat與SpringMVC集成

  1. 在SpringApplication的run方法中,會進行ApplicationContext的創建,然后會執行 refreshContext 方法,其中的會執行onRefresh方法,這個方法是由子類ServletWebServerApplicationContext實作,ServletWebServerApplicationContext中onRefresh方法代碼如下,
protected void onRefresh() {
    super.onRefresh();
    try {
        // 創建 web 服務器
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
  1. createWebServer里面的邏輯主要是創建web服務器,主要代碼如下,
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    // 如果為 null,則創建
    if (webServer == null && servletContext == null) {
        // 獲取WebServerFactory
        ServletWebServerFactory factory = getWebServerFactory();
       // 獲取 webServer ,getSelfInitializer() 回傳的是 函式式介面
	   // 是一個 實作了@FunctionalInterface 介面的串列
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}
  1. 創建服務器分為兩步,第一步獲取WebServerFactory 服務器工廠,第二步根據工廠獲取具體的web服務器,getWebServerFactory()主要邏輯為:先獲 BeanFactory ,然后從 BeanFactory取 ServletWebServerFactory 型別的 BeanNames陣列,如果陣列長度為 0,則拋例外,如果陣列長度大于 1,則拋例外,最后根據第一個beanName和ServletWebServerFactory型別獲取容器中的Bean進行回傳,
  2. getWebServerFactory方法原始碼如下,
protected ServletWebServerFactory getWebServerFactory() {
    // 先獲 BeanFactory ,再從 BeanFactory取 ServletWebServerFactory 型別的 BeanNames
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    // 如果長度為 0,則拋例外
    if (beanNames.length == 0) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
                                              + "ServletWebServerFactory bean.");
    }
    // 如果長度大于 1,則拋例外
    if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
                                              + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    // 回傳根據第一個beanName和ServletWebServerFactory型別獲取容器中的Bean
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

因為之前是注入了Tomcat的TomcatServletWebServerFactory,這里回傳的是前面注入到容器里面的 TomcatServletWebServerFactory,然后呼叫該類的getWebServer方法,傳入getSelfInitializer()方法引數,這個時候不會進行getSelfInitializer()里面具體業務邏輯呼叫,而是生成實作了ServletContextInitializer介面的匿名類后傳入方法中,ServletContextInitializer介面代碼如下,

@FunctionalInterface
public interface ServletContextInitializer {
	/**
	 * Configure the given {@link ServletContext} with any servlets, filters, listeners
	 * context-params and attributes necessary for initialization.
	 * @param servletContext the {@code ServletContext} to initialize
	 * @throws ServletException if any call against the given {@code ServletContext}
	 * throws a {@code ServletException}
	 */
	void onStartup(ServletContext servletContext) throws ServletException;
}
  1. getSelfInitializer()方法回傳的是函式運算式,不會立馬執行方法內容,當tomcat服務器啟動的時候會呼叫該匿名類的onStartup方法,getSelfInitializer方法中的主要邏輯為:找出實作了ServletContextInitializer 介面的類并注入容器 ,然后回圈呼叫實作了ServletContextInitializer 介面的匿名類的onStartup()方法,在3.1 章節中注入了實作了ServletContextInitializer 介面的DispatcherServletRegistrationBean類,這里會呼叫DispatcherServletRegistrationBean的onStartup()方法,而DispatcherServletRegistrationBean中包含了3.1中注入的DispatcherServlet 類,DispatcherServletRegistrationBean的onStartup方法的主要邏輯為:將當前類中的DispatcherServlet 添加到Tomcat 的 servletContext 中,getSelfInitializer關鍵代碼如下,
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    // 注入 servletContext 型別的 bean
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // 注入實作了ServletContextInitializer 介面的類 ,并進行回圈呼叫onStartup
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

上面的getServletContextInitializerBeans()方法主要邏輯為:創建一個ServletContextInitializerBeans類,它實作了AbstractCollection介面,可以作為集合直接遍歷,ServletContextInitializerBeans構造方法主要邏輯為:在容器中獲取所有實作了ServletContextInitializer 介面的類添加到this.initializers中,排序后賦值給sortedList屬性,getServletContextInitializerBeans()和ServletContextInitializerBeans的構造方法代碼如下,

//getServletContextInitializerBeans具體代碼
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    // 構造ServletContextInitializerBeans物件,該物件實作了 AbstractCollection<ServletContextInitializer> 介面
    // 是 ServletContextInitializer的 一個集合類,
    return new ServletContextInitializerBeans(getBeanFactory());
}

// ServletContextInitializerBeans 構造方法
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
                                      Class<? extends ServletContextInitializer>... initializerTypes) {
    // 初始化 initializers
    this.initializers = new LinkedMultiValueMap<>();
    this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
        : Collections.singletonList(ServletContextInitializer.class);
    // 在beanFactory中查找實作了 ServletContextInitializer 介面的類
    // 這里包括了  ServletRegistrationBean 、FilterRegistrationBean
    // DelegatingFilterProxyRegistrationBean、ServletListenerRegistrationBean 和其他,
    // 添加到 initializers 集合中
    addServletContextInitializerBeans(beanFactory);
    // 添加適配的 Bean
    addAdaptableBeans(beanFactory);
    // initializers 排序
    List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
        .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
        .collect(Collectors.toList());
    // 給該集合可遍歷物件賦值
    this.sortedList = Collections.unmodifiableList(sortedInitializers);
    // 回傳 initializers
    logMappings(this.initializers);
}

addServletContextInitializerBeans()方法中會找到所有實作了 ServletContextInitializer 介面的類,即會找到3.1章節中決議的DispatcherServletRegistrationBean類,都添加到 this.initializers 集合中,

? 5.1DispatcherServletRegistrationBean的onStartup方法主要邏輯為:把當前的DispacherServlet物件添加到從引數傳進來的ServletContext物件中,onStartup具體代碼如下,

public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    // 注冊 Servlet 到 tomcat的 servletContext中
    register(description, servletContext);
}

? 5.2 register()方法是具體注冊當前Servlet到tomcat的 servletContext中,代碼如下,

@Override
protected final void register(String description, ServletContext servletContext) {
    // 添加當前的 servlet 到 tomcat 的 servletContext 中
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
        return;
    }
    configure(registration);
}

? 5.3 addRegistration方法是先獲取servlet名稱再注冊當前Servlet到引數傳進來的的servletContext引數中,代碼如下

@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    // 獲取 當前物件的 servlet 名稱
    String name = getServletName();
    //添加當前的 servlet 到 tomcat 的 servletContext 中
    return servletContext.addServlet(name, this.servlet);
}

? 5.4總結一下,這個getSelfInitializer()很關鍵,主要邏輯為:把springmvc中的DispatcherServlet 添加到tomcat服務器中的servletContext物件中 ,

  1. getWebServer方法用來獲取WebServer,大概邏輯是:先創建Tomcat類,再初始化相關屬性,最后創建TomcatWebServer物件,具體的代碼如下,
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    // 創建具體的Tomcat類
    Tomcat tomcat = new Tomcat();
    // 獲取主路徑,并且設定主路徑
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // 創建連接器和協議
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    // 添加連接器
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    // 設定 host 的自動部署屬性為 false
    tomcat.getHost().setAutoDeploy(false);
    // 配置 engine
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    // 初始化 TomcatEmbeddedContext ,并把 TomcatEmbeddedContext 添加至 host 中
	// 用 initializersToUse 初始化 TomcatStarter ,并設定 TomcatEmbeddedContext 的Starter屬性為  TomcatStarter 
    // initializersToUse 為函式式介面 ,即 實作了ServletContextInitializer介面 的匿名類
    prepareContext(tomcat.getHost(), initializers);
    // 創建具體的 TomcatwebServer
    return getTomcatWebServer(tomcat);
}
  1. prepareContext()方法是創建WebServer前的準備 ,主要邏輯為:創建 TomcatEmbeddedContext ,并把 TomcatEmbeddedContext 添加至 host 中,然后用 initializersToUse (initializersToUse 為實作了ServletContextInitializer介面 的集合,其中包括了getWebServer方法中的引數 initializers )初始化 TomcatStarter ,并設定 TomcatEmbeddedContext 的Starter屬性為TomcatStarter,即TomcatEmbeddedContext的Starter屬性為TomcatStarter,在tomcat啟動的時候會呼叫到TomcatStarter的onStartup方法,
  2. getTomcatWebServer()中會創建TomcatwebServer類,并且傳入 tomcat 和 埠是否大于等于0 兩個引數,代碼如下
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0);
}

TomcatWebServer的建構式中,除了給tomcat和 autoStart賦值,還會呼叫初始化方法initialize,TomcatWebServer的構造方法代碼如下,

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    // 自動開啟
    this.autoStart = autoStart;
    // 初始化方法
    initialize();
}
  1. initialize()方法中會進行TomcatWebServer的初始,關鍵代碼如下,
// Start the server to trigger initialization listeners
// tomcat 服務器啟動
this.tomcat.start();

整個tomcat的啟動比較復雜,有興趣的可以去研究下tomcat原始碼,這里不做多講直接給出結論,其中會呼叫上面創建的TomcatStarter類的onStartup方法,并傳入tomcat的servletContext物件,TomcatStarter的onStartup具體代碼如下,

public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        // 遍歷已經注冊的 initializers, 
        for (ServletContextInitializer initializer : this.initializers) {
            initializer.onStartup(servletContext);
        }
    }
    catch (Exception ex) {
        this.startUpException = ex;
        // Prevent Tomcat from logging and re-throwing when we know we can
        // deal with it in the main thread, but log for information here.
        if (logger.isErrorEnabled()) {
            logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
                         + ex.getMessage());
        }
    }
}

onStartup方法中會遍歷 initializers 集合并且呼叫其onStartup()方法,而initializers包括了我們在呼叫getWebServer時傳入的getSelfInitializer方法,該方法體的主要業務邏輯上面已經講了(請看本章的第5點),就是給傳進去的servletContext添加當前Spring容器中注入的SpringMVC的DispatchServlet類,

  1. 最后tomcat完成初始化,會監聽一個具體的埠,至此,tomcat啟動的程序已經把SpringMVC的DispatchServlet類添加到了tomcat的servletContext物件中,當請求進來Web服務器的時候會轉到DispatchServlet中,DispatchServlet的整個初始化程序這里不細講了,請參考 SpringMVC請求流程原始碼分析 文章,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/500186.html

標籤:其他

上一篇:Python 實作貪心演算法

下一篇:Java集合的lastlastIndexOfSubList()方法具有什么功能呢?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more