溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

web.xml?SpringBoot打包可執(zhí)行Jar運行SpringMVC的方法是什么

發(fā)布時間:2023-04-03 13:47:10 來源:億速云 閱讀:140 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“web.xml SpringBoot打包可執(zhí)行Jar運行SpringMVC的方法是什么”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“web.xml SpringBoot打包可執(zhí)行Jar運行SpringMVC的方法是什么”吧!

部署到webapps目錄啟動

本文使用的Spring版本為Spring6,SpringBoot版本為3,JDK為17,可能會和之前有細微不同,但整體流程差不太大。

如果部署應用到tomcat webapps目錄下面啟動,則需要在項目中配置web.xml文件

web.xml文件

配置Spring應用上下文

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/application-context.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

context-param

context-param標簽是用于在Web應用程序的上下文范圍內(nèi)設置初始化參數(shù)。這些參數(shù)可以在整個Web應用程序中使用,并且可以通過ServletContext對象的getInitParameter()方法獲取。

ContextLoaderListener

ContextLoaderListener實現(xiàn)了ServletContextListener接口,這個接口是tomcat留給應用程序初始化上下文環(huán)境的接口,用于在Web應用程序啟動時加載ApplicationContext。

ServletContextListener有兩個默認方法

// 在所有的servlet和filter初始化之前被調(diào)用
default public void contextInitialized(ServletContextEvent sce) {
}
// 在所有的servlet和filter銷毀之后被調(diào)用
default public void contextDestroyed(ServletContextEvent sce) {
}

ContextLoaderListener還繼承了ContextLoader類,所有的context操作都在此類進行。

ContextLoaderListener實現(xiàn)contextInitialized方法,然后調(diào)用父類ContextLoader的initWebApplicationContext方法,把ServletContext傳進去。

@Override
public void contextInitialized(ServletContextEvent event) {
 &nbsp; initWebApplicationContext(event.getServletContext());
}

初始化Spring Context。

initWebApplicationContext方法關鍵代碼

...
if (this.context == null) {
 &nbsp; &nbsp;// 創(chuàng)建ApplicationContext
    this.context = createWebApplicationContext(servletContext);
}
...
// 刷新ApplicationContext
configureAndRefreshWebApplicationContext(cwac, servletContext);
...
// 將當前ApplicationContext添加到ServletContext的屬性中,后面有用再說
// String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
...

創(chuàng)建ApplicationContext

在createWebApplicationContext方法中,先調(diào)用determineContextClass方法確定使用哪個ApplicationContext,找到之后,實例化。

determineContextClass這個方法,主要是確定使用的ApplicationContext,首先從web.xml中加載,如果用戶有定義,直接使用用戶自定義的。

String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);

web.xml中配置如下,

<context-param>
    <param-name>contextClass</param-name>
    <param-value>com.xxx.XxxContext</param-value>
</context-param>

如果沒有配置,則使用Spring默認的XmlWebApplicationContext類。

這個類在ContextLoader同路徑包下面的ContextLoader.properties文件中定義。

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

配置和刷新ApplicationContext

configureAndRefreshWebApplicationContext關鍵代碼

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,ServletContext sc) {
    // ...
    // 獲取web.xml中配置的contextConfigLocation參數(shù)
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }
    // ...
    // 刷新上下文
    wac.refresh();
}

至此Tomcat已經(jīng)啟動Spring環(huán)境了,后續(xù)就是Spring的初始化流程,這里不再敘述。

初始化DispatcherServlet

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>/WEB-INF/spring/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

此處的contextConfigLocation屬于DispatcherServlet的父類FrameworkServlet,主要用來加載SpringMVC相關的配置,示例如下:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
    <!-- 掃描控制器和其他組件 -->
    <context:component-scan base-package="com.example.controller" />
    <!-- 配置視圖解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>
    <!-- 啟用Spring MVC注解支持 -->
    <mvc:annotation-driven />
</beans>

DispatcherServlet類圖

web.xml?SpringBoot打包可執(zhí)行Jar運行SpringMVC的方法是什么

可以看到DispatcherServlet實現(xiàn)了Servlet接口,Servlet接口中有init方法,SpringMVC的配置就是在初始化的時候被加載的。

關鍵代碼在HttpServletBean.init()和FrameworkServlet.initServletBean()方法中。

HttpServletBean.init()

public final void init() throws ServletException {
   // Set bean properties from init parameters.
   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
         initBeanWrapper(bw);
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
         }
         throw ex;
      }
   }
   // Let subclasses do whatever initialization they like.
   initServletBean();
}

FrameworkServlet.initServletBean()

protected final void initServletBean() throws ServletException {
    ...
    // 在這里初始化ApplicationContext 
    this.webApplicationContext = initWebApplicationContext();
    // 初始化servlet
    initFrameworkServlet();
}

FrameworkServlet.initWebApplicationContext()

protected WebApplicationContext initWebApplicationContext() {
    // 此處獲取根容器,就是Spring初始化的XmlWebApplicationContext,
    // 在上面把它添加到了ServletContext的屬性中,標記根容器,這里把它獲取出來
    // String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    // servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;
   // 此時webApplicationContext還是null,因為DispatchServlet是被tomcat創(chuàng)建的,需要無參構(gòu)造器
   // 構(gòu)造器中沒有設置webApplicationContext的代碼,所以此時webApplicationContext還是null
   // 注意:在SpringBoot使用嵌入式Tomcat時,這個webApplicationContext不為null,因為FrameworkServlet還
   // 實現(xiàn)了ApplicationContextAware接口,所以當SpringBoot的上下文準備好之后,會回調(diào)setApplicationContext方法
   // 注入ApplicationContext,后面在細說 
   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
         // The context has not yet been refreshed -> provide services such as
         // setting the parent context, setting the application context id, etc
         if (cwac.getParent() == null) {
            // The context instance was injected without an explicit parent -> set
            // the root application context (if any; may be null) as the parent
            cwac.setParent(rootContext);
         }
         configureAndRefreshWebApplicationContext(cwac);
      }
   }
   if (wac == null) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      // 此處主要是獲取web.xml配置的WebApplicationContext
      // 可以通過設置參數(shù)contextAttribute來設置加載SpringMVC的ApplicationContext
      // 比如下面這樣。除非項目中有多個WebApplicationContext,需要使用其他WebApplicationContext才會用到
      // 一般都是null
      // <context-param>
      //    <param-name>contextAttribute</param-name>
      //    <param-value>myWebApplicationContext</param-value>
      // </context-param>
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // 現(xiàn)在進入到創(chuàng)建SpringMVC的ApplicationContext流程
      // 也就是加載contextConfigLocation定義的xml文件 
      // No context instance is defined for this servlet -> create a local one
      wac = createWebApplicationContext(rootContext);
   }
   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
         // 初始化策略對象
         // 比如:HandlerMapping,HandlerAdapter,ViewResolver等等 
         onRefresh(wac);
      }
   }
   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }
   return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
   // SpringMVC所使用的contextClass,可以在<servlet>標簽下設置
   // <init-param>
   //    <param-name>contextClass</param-name>
   //    <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
   // </init-param>
   // 默認為XmlWebApplicationContext
   Class<?> contextClass = getContextClass();
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
   }
   // 實例化ApplicationContext
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
   // 設置環(huán)境參數(shù)
   wac.setEnvironment(getEnvironment());
   // 設置父容器為Spring的ApplicationContext
   wac.setParent(parent);
   // 獲取SpringMVC的contextConfigLocation文件
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   // 配置并刷新ApplicationContext
   configureAndRefreshWebApplicationContext(wac);
   return wac;
}

DispatchServlet初始化完成

為什么需要父子容器

父子容器的作用主要是劃分框架邊界和實現(xiàn)bean的復用。

  • 在J2EE三層架構(gòu)中,在service層我們一般使用Spring框架,而在web層則有多種選擇,如Spring MVC、Struts等。為了讓web層能夠使用service層的bean,我們需要將service層的容器作為web層容器的父容器,這樣就可以實現(xiàn)框架的整合。

  • 父子容器的作用在于,當我們嘗試從子容器(Servlet WebApplicationContext)中獲取一個bean時,如果找不到,則會委派給父容器(Root WebApplicationContext)進行查找。這樣可以避免在多個子容器中重復定義相同的bean,提高了代碼的復用性和可維護性。

接收請求

請求先進入doService,然后調(diào)用doDispatch進行處理。

doDispatch關鍵代碼

...
// 首先根據(jù)當前請求HttpServletRequest,遍歷所有的HandlerMapping執(zhí)行handle方法,返回可用的HandlerExecutionChain對象。
mappedHandler = getHandler(processedRequest);
// 然后根據(jù)handler獲取支持的適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 執(zhí)行HandlerInterceptor.preHandle,在controller的方法被調(diào)用前執(zhí)行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}
// 執(zhí)行controller方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 執(zhí)行HandlerInterceptor.postHandle,在controller的方法被調(diào)用后執(zhí)行
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 渲染結(jié)果到視圖
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  • HandlerMapping是request與handler object之間的映射,它能根據(jù)request找到對應的handler。handler object可以是任意類型,比如@Controller注解的類,或者實現(xiàn)了Controller接口的類,或者實現(xiàn)了HttpRequestHandler接口的類等。

  • HandlerExecutionChain是handler執(zhí)行鏈,它包裝了handler object和一組HandlerInterceptor。HandlerInterceptor是攔截器,它可以在handler執(zhí)行前后進行一些額外的操作,比如權(quán)限檢查,日志記錄等。

  • HandlerAdapter是handler的適配器,它能處理不同類型的handler object,并調(diào)用其對應的方法,返回ModelAndView對象。HandlerAdapter可以根據(jù)handler object的類型,進行參數(shù)綁定,返回值處理等操作。

HandlerInterceptor使用

  • 定義一個攔截器類,實現(xiàn)HandlerInterceptor接口或者繼承HandlerInterceptorAdapter類,重寫preHandle,postHandle和afterCompletion三個方法。

  • 在preHandle方法中,可以獲取請求和響應對象,進行預處理,比如檢查請求頭中的token,或者判斷請求的url是否有權(quán)限訪問等。如果返回true,則繼續(xù)執(zhí)行后續(xù)的攔截器或者處理器;如果返回false,則中斷請求,不再執(zhí)行后續(xù)的攔截器或者處理器。

  • 在postHandle方法中,可以獲取請求和響應對象,以及處理器返回的ModelAndView對象,進行后處理,比如修改模型數(shù)據(jù)或者視圖信息等。這個方法只有在preHandle返回true且處理器成功執(zhí)行后才會調(diào)用。

  • 在afterCompletion方法中,可以獲取請求和響應對象,以及處理器拋出的異常對象(如果有的話),進行清理資源或者異常處理等。這個方法只有在preHandle返回true后才會調(diào)用,無論處理器是否成功執(zhí)行。

  • 在SpringMVC的配置文件中,注冊攔截器類,并指定攔截的url模式。可以注冊多個攔截器,并指定順序。攔截器會按照順序執(zhí)行preHandle方法,然后按照逆序執(zhí)行postHandle和afterCompletion方法。

HandlerInterceptor和Filter的區(qū)別

  • HandlerInterceptor是基于Java反射機制的,而Filter是基于函數(shù)回調(diào)的。HandlerInterceptor可以利用Spring的AOP技術(shù),實現(xiàn)更靈活的攔截邏輯,而Filter只能在請求前后進行簡單的處理。

  • HandlerInterceptor不依賴于Servlet容器,而Filter依賴于Servlet容器。HandlerInterceptor是SpringMVC框架提供的,可以在任何情況下使用,而Filter是Servlet規(guī)范的一部分,只能在Web應用中使用。

  • HandlerInterceptor的執(zhí)行由SpringMVC框架控制,而Filter的執(zhí)行由Servlet容器控制。HandlerInterceptor可以通過IoC容器來管理,可以注入其他的Bean,而Filter則需要在web.xml中配置,或者使用@WebFilter注解,并且需要@ServletComponentScan掃描。

  • HandlerInterceptor只能攔截DispatcherServlet處理的請求,而Filter可以攔截任何請求。HandlerInterceptor只能對Controller方法進行攔截,而Filter可以對靜態(tài)資源、JSP頁面等進行攔截。

  • HandlerInterceptor有三個方法:preHandle,postHandle和afterCompletion,分別在請求處理前后和視圖渲染前后執(zhí)行,而Filter只有一個方法:doFilter,在請求處理前后執(zhí)行。

處理controller返回結(jié)果

對于被controller方法,使用的適配器是RequestMappingHandlerAdapter,在handlerAdapter.handle方法執(zhí)行時,會去執(zhí)行對應的controller方法,處理controller方法返回的結(jié)果。

invocableMethod.invokeAndHandle(webRequest, mavContainer);

ServletInvocableHandlerMethod.invokeAndHandle

// 執(zhí)行controller方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
...
// 處理返回數(shù)據(jù),會判斷是不是有@ResponseBody注解,如果有,會使用RequestResponseBodyMethodProcessor來處理返回值
// 然后會解析請求頭等等,判斷應該返回什么類型的數(shù)據(jù),然后使用對應的HttpMessageConverter寫入輸出流
this.returnValueHandlers.handleReturnValue(
      returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

SpringBoot Jar啟動

SpringBoot使用嵌入式Servlet容器啟動應用,有Tomcat,Jetty,Undertow。

選擇Servlet容器

SpringBoot默認使用Tomcat,可以在配置文件中看出。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

web模塊自動引入了tomcat

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>

如果不使用Tomcat可以排除,引入其他服務器。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <!-- 剔除Tomcat -->
  <exclusions>
    <exclusion>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <groupId>org.springframework.boot</groupId>
    </exclusion>
  </exclusions>
</dependency>
<!-- 使用jetty -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

如果沒有排除Tomcat,直接引入其他服務器,比如下面。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <!-- 沒有排除Tomcat -->
</dependency>
<!-- 引入jetty -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

如果項目中同時引入了Tomcat和其他服務器的依賴,那么SpringBoot會按照以下順序來選擇啟動的服務器。

Tomcat > Jetty > Undertow

也就是說,如果有Tomcat,就優(yōu)先使用Tomcat,如果沒有Tomcat,就看有沒有Jetty,如果有Jetty,就使用Jetty,以此類推。這個順序是在SpringBoot的ServletWebServerFactoryConfiguration類中定義的。

// 只展示必要代碼
class ServletWebServerFactoryConfiguration {
   // 當Servlet、Tomcat、UpgradeProtocol類在類路徑存在時
   // 并且ServletWebServerFactory類存在,則會創(chuàng)建tomcatServletWebServerFactory bean。
   @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
   @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
   static class EmbeddedTomcat {
      @Bean
      TomcatServletWebServerFactory tomcatServletWebServerFactory(
         ... 代碼省略
      }
   }
   // 當Servlet、Server、WebAppContext類在類路徑存在時
   // 并且ServletWebServerFactory類型的Bean不存在時,則會創(chuàng)建JettyServletWebServerFactory bean。
   // ServletWebServerFactory是TomcatServletWebServerFactory、JettyServletWebServerFactory、
   // UndertowServletWebServerFactory的父類
   // 所以如果Tomcat被引入,上面的tomcatServletWebServerFactory就會被創(chuàng)建,這里的條件就不滿足,不會被創(chuàng)建。
   @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
   @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
   static class EmbeddedJetty {
      @Bean
      JettyServletWebServerFactory JettyServletWebServerFactory(
         ... 代碼省略
      }
   }
   // 分析同上
   @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
   @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
   static class EmbeddedUndertow {
      @Bean
      UndertowServletWebServerFactory undertowServletWebServerFactory(
        ... 代碼省略
      }
   }

下面繼續(xù)以Tomcat為例

Tomcat配置、啟動

Tomcat是在Spring容器啟動的時候啟動的

SpringApplication.run方法

首先創(chuàng)建一個ConfigurableApplicationContext對象,并調(diào)用其refresh()方法,這個對象一般是AnnotationConfigServletWebServerApplicationContext。

context = createApplicationContext();
-> refreshContext(context);
-> refresh(context);
-> applicationContext.refresh();

refresh()方法會調(diào)用其父類ServletWebServerApplicationContext的refresh()方法,在父類的refresh()中再次調(diào)用父類AbstractApplicationContext的refresh()方法,主要在onRefresh階段,會進行服務器的配置。

... refresh()代碼簡略
// 這里會初始化Tomcat配置
onRefresh();
// 這里會啟動Tomcat
finishRefresh();
...

回到ServletWebServerApplicationContext類的onRefresh()方法,會調(diào)用createWebServer()方法,創(chuàng)建web服務器。

protected void onRefresh() {
   super.onRefresh();
   try {
      // 創(chuàng)建服務器
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}
private void createWebServer() {
    ... 代碼簡略
    // 獲取工廠類,這里獲取的就是在配置類中生效的那一個,這里為TomcatServletWebServerFactory
    ServletWebServerFactory factory = getWebServerFactory();
    createWebServer.tag("factory", factory.getClass().toString());
    // 獲取服務器 
    this.webServer = factory.getWebServer(getSelfInitializer());  
}

TomcatServletWebServerFactory.getWebServer

public WebServer getWebServer(ServletContextInitializer... initializers) {
   if (this.disableMBeanRegistry) {
      Registry.disableRegistry();
   }
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   for (LifecycleListener listener : this.serverLifecycleListeners) {
      tomcat.getServer().addLifecycleListener(listener);
   }
   // 設置Connector,對應與Tomcat Server.xml 中的<Connector></Connector>
   Connector connector = new Connector(this.protocol);
   connector.setThrowOnFailure(true);
   // 對應于Server.xml 中
   // <Service name="Catalina">
   //   <Connector port="8080" protocol="HTTP/1.1"
   //     connectionTimeout="20000"
   //     redirectPort="8443" relaxedQueryChars="[|]"/>
   // </Service>
   tomcat.getService().addConnector(connector);
   customizeConnector(connector);
   tomcat.setConnector(connector);
   tomcat.getHost().setAutoDeploy(false);
   configureEngine(tomcat.getEngine());
   for (Connector additionalConnector : this.additionalTomcatConnectors) {
      tomcat.getService().addConnector(additionalConnector);
   }
   // 準備好Context組件
   prepareContext(tomcat.getHost(), initializers);
   return getTomcatWebServer(tomcat);
}
// 創(chuàng)建Tomcat服務器
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
   return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}

至此,Tomcat配置已經(jīng)初始化完成,準備啟動。

在finishRefresh()方法中,會啟動Tomcat

getLifecycleProcessor().onRefresh();
> DefaultLifecycleProcessor.startBeans(true);
> LifecycleGroup::start
> doStart(this.lifecycleBeans, member.name, this.autoStartupOnly);
> bean.start();
> WebServerStartStopLifecycle.start
> TomcatWebServer.start();
private void startBeans(boolean autoStartupOnly) {
   Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
   Map<Integer, LifecycleGroup> phases = new TreeMap<>();
   lifecycleBeans.forEach((beanName, bean) -> {
      if (!autoStartupOnly || (bean instanceof SmartLifecycle smartLifecycle && smartLifecycle.isAutoStartup())) {
         int phase = getPhase(bean);
         phases.computeIfAbsent(
               phase,
               p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)
         ).add(beanName, bean);
      }
   });
   if (!phases.isEmpty()) {
      phases.values().forEach(LifecycleGroup::start);
   }
}
public void start() {
   this.webServer.start();
   this.running = true;
   this.applicationContext
      .publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}

DispatchServlet配置

ServletContextInitializer

在prepareContext方法中,有一個方法configureContext

configureContext(context, initializersToUse);

configureContext方法,在這里面創(chuàng)建了一個TomcatStarter對象,這個類實現(xiàn)了ServletContainerInitializer接口,所以在容器啟動過程中會被調(diào)用。

TomcatStarter starter = new TomcatStarter(initializers);
context.addServletContainerInitializer(starter, NO_CLASSES);

initializers是Spring自己定義的初始化接口ServletContextInitializer,傳入TomcatStarter之后,在onStartup方法中循環(huán)調(diào)用onStartup方法。

public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
   try {
      for (ServletContextInitializer initializer : this.initializers) {
         initializer.onStartup(servletContext);
      }
   }
   ...
}

需要注意的是,這里的initializers有些傳過來的時候是一個函數(shù)式接口,在上面的factory.getWebServer(getSelfInitializer());這里傳進來的,就是一個函數(shù)式接口

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
   return this::selfInitialize;
}

實際調(diào)用在下面這個方法

private void selfInitialize(ServletContext servletContext) throws ServletException {
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
   }
}

這里遍歷所有的ServletContextInitializer,然后調(diào)用它的onStartup方法。

其中有一個DispatcherServletRegistrationBean,這個類實現(xiàn)了ServletContextInitializer接口,主要是用來添加DispatchServlet。

DispatcherServletAutoConfiguration配置類中有DispatcherServlet,DispatcherServletRegistrationBean兩個Bean。

protected static class DispatcherServletRegistrationConfiguration {
   @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
   @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
   public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
         WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
      // 創(chuàng)建DispatcherServletRegistrationBean,并把dispatcherServlet傳進去
      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;
   }
}
protected static class DispatcherServletConfiguration {
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
        // 創(chuàng)建DispatcherServlet
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
        return dispatcherServlet;
    }
}

ServletContextInitializer.onStartup方法由子類RegistrationBean實現(xiàn)

public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    // register是一個抽象方法,由子類DynamicRegistrationBean實現(xiàn)
    register(description, servletContext);
}
protected abstract void register(String description, ServletContext servletContext);

DynamicRegistrationBean.register

protected final void register(String description, ServletContext servletContext) {
   // addRegistration是一個抽象方法,由子類ServletRegistrationBean實現(xiàn)
   D registration = addRegistration(description, servletContext);
   if (registration == null) {
      logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
      return;
   }
   // Servlet被添加到Context后,這里對Servlet進行配置,如攔截路徑 
   configure(registration);
}
protected abstract D addRegistration(String description, ServletContext servletContext);

ServletRegistrationBean.addRegistration,作用類似下面

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
   String name = getServletName();
   // 添加Servlet到Context中,這里的servlet就是DispatchServlet。 
   return servletContext.addServlet(name, this.servlet);
}

ServletRegistrationBean.configure,作用類似下面

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
protected void configure(ServletRegistration.Dynamic registration) {
   super.configure(registration);
   String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
   if (urlMapping.length == 0 && this.alwaysMapUrl) {
      // DEFAULT_MAPPINGS默是“/” 
      urlMapping = DEFAULT_MAPPINGS;
   }
   if (!ObjectUtils.isEmpty(urlMapping)) {
      // 設置mapping 
      registration.addMapping(urlMapping);
   }
   registration.setLoadOnStartup(this.loadOnStartup);
   if (this.multipartConfig != null) {
      registration.setMultipartConfig(this.multipartConfig);
   }
}

至此,DispatchServlet已配置好,后續(xù)流程和web.xml配置調(diào)用流程基本相同。

FrameworkServlet.initWebApplicationContext()

protected WebApplicationContext initWebApplicationContext() {
    // 此處獲取根容器,就是Spring初始化的XmlWebApplicationContext,
    // 在上面把它添加到了ServletContext的屬性中,標記根容器,這里把它獲取出來
    // String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    // servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    // ===========上面為使用web.xml時的分析,下面為SpringBoot嵌入式Tomcat分析============
    // 同樣是獲取根容器,不過一般為AnnotationConfigServletWebServerApplicationContext
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;
   // 此時webApplicationContext還是null,因為DispatchServlet是被tomcat創(chuàng)建的,需要無參構(gòu)造器
   // 構(gòu)造器中沒有設置webApplicationContext的代碼,所以此時webApplicationContext還是null
   // ===========上面為使用web.xml時的分析,下面為SpringBoot嵌入式Tomcat分析============
   // 注意:在SpringBoot使用嵌入式Tomcat時,這個webApplicationContext不為null,因為FrameworkServlet還
   // 實現(xiàn)了ApplicationContextAware接口,所以當SpringBoot的上下文準備好之后,會回調(diào)setApplicationContext方法
   // 注入ApplicationContext,后面在細說 
   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
         // The context has not yet been refreshed -> provide services such as
         // setting the parent context, setting the application context id, etc
         if (cwac.getParent() == null) {
            // The context instance was injected without an explicit parent -> set
            // the root application context (if any; may be null) as the parent
            cwac.setParent(rootContext);
         }
         configureAndRefreshWebApplicationContext(cwac);
      }
   }
   if (wac == null) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      // 此處主要是獲取web.xml配置的WebApplicationContext
      // 可以通過設置參數(shù)contextAttribute來設置加載SpringMVC的ApplicationContext
      // 比如下面這樣。除非項目中有多個WebApplicationContext,需要使用其他WebApplicationContext才會用到
      // 一般都是null
      // <context-param>
      //    <param-name>contextAttribute</param-name>
      //    <param-value>myWebApplicationContext</param-value>
      // </context-param>
      // ===========上面為使用web.xml時的分析,下面為SpringBoot嵌入式Tomcat分析
      // 因為wac此時不為null,這里不會進入
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // 現(xiàn)在進入到創(chuàng)建SpringMVC的ApplicationContext流程
      // 也就是加載contextConfigLocation定義的xml文件 
      // ===========上面為使用web.xml時的分析,下面為SpringBoot嵌入式Tomcat分析
      // 因為wac此時不為null,這里不會進入,所以沒有SpringMVC的容器,也就是沒有父子容器之分,SpringBoot項目中只有一個容器
      // No context instance is defined for this servlet -> create a local one
      wac = createWebApplicationContext(rootContext);
   }
   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
         // 初始化策略對象
         // 比如:HandlerMapping,HandlerAdapter,ViewResolver等等
         onRefresh(wac);
      }
   }
   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }
   return wac;
}

感謝各位的閱讀,以上就是“web.xml SpringBoot打包可執(zhí)行Jar運行SpringMVC的方法是什么”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對web.xml SpringBoot打包可執(zhí)行Jar運行SpringMVC的方法是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI