日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

从web.xml谈谈SpringMVC集成spring的初始化流程及SpringBoot集成SpringMVC

發布時間:2025/3/15 javascript 13 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从web.xml谈谈SpringMVC集成spring的初始化流程及SpringBoot集成SpringMVC 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、關于servlet

  • 詳解servlet,https://www.runoob.com/servlet/servlet-tutorial.html
  • 總覽一下:
    • servlet與servlet容器

    Java Servlet(Java服務器小程序)是一個基于Java技術的Web組件,運行在服務器端,它由Servlet容器所管理,用于生成動態的內容。 Servlet是平臺獨立的Java類,編寫一個Servlet,實際上就是按照Servlet規范編寫一個Java類。Servlet被編譯為平臺獨立 的字節碼,可以被動態地加載到支持Java技術的Web服務器中運行。

    • Servlet容器也叫做Servlet引擎,是Web服務器或應用程序服務器的一部分,用于在發送的請求和響應之上提供網絡服務,解碼基于 MIME的請求,格式化基于MIME的響應。Servlet沒有main方法,不能獨立運行,它必須被部署到Servlet容器中,由容器來實例化和調用 Servlet的方法(如doGet()和doPost()),Servlet容器在Servlet的生命周期內包容和管理Servlet。在JSP技術 推出后,管理和運行Servlet/JSP的容器也稱為Web容器。

    (注:常用的MIME類型:text/html,application/pdf,video/quicktime,application /java,image/jpeg,application/jar,application/octet-stream,application/x- zip)

    有了servlet之后,用戶通過單擊某個鏈接或者直接在瀏覽器的地址欄中輸入URL來訪問Servlet,Web服務器接收到該請求后,并不是將 請求直接交給Servlet,而是交給Servlet容器。Servlet容器實例化Servlet,調用Servlet的一個特定方法對請求進行處理, 并產生一個響應。這個響應由Servlet容器返回給Web服務器,Web服務器包裝這個響應,以HTTP響應的形式發送給Web瀏覽器。

    • servlet容器能提供什么?

    我們知道需要由servlet容器來管理和運行servlet,但是為什么要這樣做呢?使用servlet容器的原因有:

    • 通信支持:利用容器提供的方法,你能輕松的讓servlet與web服務器對話,而不用自己建立serversocket、監聽某個端口、創建流等等。容器知道自己與web服務器之間的協議,所以你的servlet不用擔心web服務器(如Apache,jetty)和你自己的web代碼之間的API,只需要考 慮如何在servlet中實現業務邏輯(如處理一個訂單)。

    • 生命周期管理:servlet容器控制著servlet的生與死,它負責加載類、實例化和初始化servlet,調用servlet方法,以及使servlet實例被垃圾回收,有了servlet容器,你不需要太多的考慮資源管理。

    • 多線程支持:容器會自動為它所接收的每個servlet請求創建一個新的java線程。針對用戶的請求,如果servlet已經運行完相應的http服務方法,這個線程就會結束。這并不是說你不需要考慮線程安全性,其實你還會遇到同步問題,不過這樣能使你少做很多工作。

    • 聲明方式實現安全:利用servlet容器,你可以使用xml部署描述文件來配置和修改安全性,而不必將其硬編碼寫到servlet類代碼中。

    • JSP支持:servlet容器負責將jsp代碼翻譯為真正的java代碼。

    • Servlet具有以下優點:
    • Servlet是單實例多線程的運行方式,每個請求在一個獨立的線程中運行,而提供服務的Servlet實例只有一個。
    • Servlet具有可升級性,能響應更多的請求,因為Servlet容器使用一個線程而不是操作系統進程,而線程僅占用有限的系統資源。
    • Servlet使用標準的API,被更多的Web服務器所支持。
    • Servlet使用Java語言編寫,因此擁有Java程序語言的所有優點,包括容易開發和平臺獨立性。
    • Servlet可以訪問Java平臺豐富的類庫,使得各種應用的開發更為容易。
    • Servlet容器給Servlet提供額外的功能,如錯誤處理和安全。

    其實,servlet就是一種使用http協議在服務器與客戶端之間通信的技術。是Socket的一種應用。

    • Tomcat

    學習Servlet技術,就需要有一個Servlet運行環境,也就是需要有一個Servlet容器,本文用的是Tomcat。還有其他如jetty。Tomcat和IIS、Apache等Web服務器一樣,具有處理HTML頁面的功能,另外它還是一個Servlet和JSP容器,獨立的 Servlet容器是Tomcat的默認模式。不過,Tomcat處理靜態HTML的能力不如Apache,我們可以將Apache和Tomcat集成在 一起使用,Apache作為HTTP Web服務器,Tomcat作為Web容器。關于apache和tomcat的區別。

    Tomcat服務器接受客戶請求并做出響應的過程如下:

  • 客戶端(通常都是瀏覽器)訪問Web服務器,發送HTTP請求。
  • Web服務器接收到請求后,傳遞給Servlet容器。
  • Servlet容器加載Servlet,產生Servlet實例后,向其傳遞表示請求和響應的對象。
  • Servlet實例使用請求對象得到客戶端的請求信息,然后進行相應的處理。
  • Servlet實例將處理結果通過響應對象發送回客戶端,容器負責確保響應正確送出,同時將控制返回給Web服務器。
  • 二、從web.xml說起

    <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"version="5.0"><!-- spring配置文件,包括開啟bean掃描,aop,事務 --><!-- Spring加載的xml文件,不配置默認為applicationContext.xml --><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring-config.xml</param-value></context-param><!--ContextLoaderListener用于在啟動web容器的時候,去上面的位置 讀取配置文件并初始化Spring容器。啟動父容器,即IOC容器,管理Dao,Service--><!-- 該類作為spring的listener使用,它會在創建時自動查找web.xml配置的applicationContext.xml文件 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--spring mvc配置--><!-- 配置Spring MVC的DispatcherServlet,也可以配置為繼承了DispatcherServlet的自定義類,這里配置spring mvc的配置(掃描controller) --><!--用于啟動子容器,也即是springMVC容器--><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-mvc.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> </web-app>
    • spring父容器的加載

    Tomcat啟動的時候會依次加載web.xml中配置的Listener、Filter和Servlet。所以根據上面的配置,會首先加載ContextLoaderListener,這個類繼承了ContextLoader,用來初始化Spring根上下文,并將其放入ServletContext中。

    實現 javax.servlet.ServletContextListener 接口,繼承 ContextLoader 類,實現 Servlet 容器啟動和關閉時,分別初始化和銷毀 WebApplicationContext 容器

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {public ContextLoaderListener() {}/*** As of Spring 3.1, supports injecting the root web application context*/public ContextLoaderListener(WebApplicationContext context) {super(context);}/*** Initialize the root web application context.*/@Overridepublic void contextInitialized(ServletContextEvent event) {// <1> 初始化 Root WebApplicationContextinitWebApplicationContext(event.getServletContext());}/*** Close the root web application context.*/@Overridepublic void contextDestroyed(ServletContextEvent event) {// <2> 銷毀 Root WebApplicationContextcloseWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());} }

    下面就以這個為入口分析下代碼。Tomcat容器首先會調用ContextLoadListener的contextInitialized()方法,這個方法又調用了父類ContextLoader的initWebApplicationContext()方法。下面是這個方法的源代碼。

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { //如果ServletContext中已經存在Spring容器則報錯 if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");}Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.if (this.context == null) {//這里創建了webApplicationContext,默認創建的是XmlWebApplicationContext//如果想要自定義實現類,可以在web.xml的<context-param>中配置contextClass這個參數//此時的Context還沒進行配置,相當于只是個"空殼"this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent ->// determine parent for root web application context, if any.ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}//讀取Spring的配置文件,初始化父上下文環境configureAndRefreshWebApplicationContext(cwac, servletContext);}}//將根上下文存入ServletContext中。servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isDebugEnabled()) {logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}catch (Error err) {logger.error("Context initialization failed", err);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throw err;} }

    spring體系下,ApplicationContext的集成體系

    執行時序圖:

    至此,Spring的父(根)上下文已經初始化完畢,并且已經存在ServletContext中。

    • springMVC子容器的加載

    DispatcherServlet的繼承圖如下

    我們知道,加載web.xml中配置的Listener、Filter后,就會加載Servlet。在我們的web.xml中就是org.springframework.web.servlet.DispatcherServlet,而通過繼承樹我們知道,DispatcherServlet也是Servlet的實現,所以加載流程也是Servlet的流程。因為DispatcherServlet繼承了FrameworkServlet,FrameworkServlet又繼承了HttpServletBean,HttpServletBean又繼承HttpServlet并且重寫了init方法,所以創建子上下文時的入口就在這個init方法。

    • HttpServletBean.init(): 重寫 GenericServlet 中的方法,負責將 ServletConfig 設置到當前 Servlet 對象中
    public final void init() throws ServletException {//讀取Servlet配置的init-param,創建DispatcherServlet實例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;}}// HttpServletBean的這個方法中沒有做任何事情,子類FrameWorkServlet這個類的initServletBean()方法重寫了// 這個類,所以后續工作會在這個方法中執行。initServletBean();}

    下面是FrameWorkServlet這個類的initServletBean()方法

    //FrameWorkServlet.initServletBean() protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");}long startTime = System.currentTimeMillis();try {//這里是重點,初始化子Spring上下文this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException ex) {this.logger.error("Context initialization failed", ex);throw ex;}catch (RuntimeException ex) {this.logger.error("Context initialization failed", ex);throw ex;}if (this.logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +elapsedTime + " ms");} }

    下面是initWebApplicationContext()方法的具體代碼

    protected WebApplicationContext initWebApplicationContext() {// <1> 獲得根 WebApplicationContext 對象WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());// <2> 獲得 WebApplicationContext wac 對象WebApplicationContext wac = null;// 第一種情況,如果構造方法已經傳入 webApplicationContext 屬性,則直接使用if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = this.webApplicationContext;// 如果是 ConfigurableWebApplicationContext 類型,并且未激活,則進行初始化if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) { // 未激活// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}// 配置和初始化 wacconfigureAndRefreshWebApplicationContext(cwac);}}}// 第二種情況,從 ServletContext 獲取對應的 WebApplicationContext 對象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 idwac = findWebApplicationContext();}// 第三種,創建一個 WebApplicationContext 對象if (wac == null) {// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);}// <3> 如果未觸發刷新事件,則主動觸發刷新事件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) {onRefresh(wac);}}// <4> 將 context 設置到 ServletContext 中if (this.publishContext) {// 將Spring子上下文存入ServletContextString attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac; }

    最后看下DispatcherServlet中的onRefresh()方法,這個方法初始化了很多策略:
    注意:FrameWorkServlet.onRefresh()只有DispatcherServlet中進行重寫

    //初始化九大組件 protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context); }

    上面都是通過類似的機制,加載所有實現了該接口的類:

    到此為止,SpringMVC的啟動過程結束了。這邊做下SpringMVC初始化總結:

  • HttpServletBean的主要做一些初始化工作,將我們在web.xml中配置的參數設置到Servlet中;
  • FrameworkServlet主要作用是初始化Spring子上下文,設置其父上下文,并將其和ServletContext關聯;
  • DispatcherServlet:初始化各個功能的實現類。比如異常處理、視圖處理、請求映射處理等。
  • 時序圖:

    三、傳統的Spring MVC項目啟動流程

  • 如果在web.xml中配置了org.springframework.web.context.ContextLoaderListener,那么Tomcat在啟動的時候會先加載父容器,并將其放到ServletContext中;
  • 然后會加載DispatcherServlet(這塊流程建議從init方法一步步往下看,流程還是很清晰的),因為DispatcherServlet實質是一個Servlet,所以會先執行它的init方法。這個init方法在HttpServletBean這個類中實現,其主要工作是做一些初始化工作,將我們在web.xml中配置的參數書設置到Servlet中,然后再觸發FrameworkServlet的initServletBean()方法;
  • FrameworkServlet主要作用是初始化Spring子上下文,設置其父上下文,并將其放入ServletContext中;
  • FrameworkServlet在調用initServletBean()的過程中同時會觸發DispatcherServlet的onRefresh()方法,這個方法會初始化Spring MVC的各個功能組件。比如異常處理器、視圖處理器、請求映射處理等。
  • 四、SpringBoot集成SpringMVC

    我們知道,SpringBoot的自動導入機制就是依賴spring framework內部使用的通用的工廠加載機制。其導入鏈如下:

    @SpringBootApplication-> @EnableAutoConfiguration> @Import({AutoConfigurationImportSelector.class}) AutoConfigurationImportSelector類下的List<String>configurations=this.getCandidateConfigurations(annotationMetadata,attributes); protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;}

    springFactoriesLoader是spring framework內部使用的通用的工廠加載機制,其可加載并實例化可能出現在classpath上的多個jar包中的META-INF/spring.factories文件中定義的指定類型的工廠,可視為一種類似于SPI的接口。 SpringBoot利用這種SPI接口實現了autoconfiguration機制:委托SpringFactoriesLoader來加載所有配置在META-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration對應的值,spring-boot-autoconfiguration jar包中的META-INF/spring.factories中的EnableAutoConfiguration配置了眾多供springboot導入的類。我們先回想一下SpringBoot預先讀取的可配置Config類有哪個是與WebMVC相關的?經過比對我們發現在Key=EnableAutoConfiguration,Value=WebMvcAutoConfiguration就是關于SpringBoot自動配置WebMVC的可配置類,關于SpringBoot自動配置WebMVC的玄機也正是在這個配置類里面。

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\

    于是,我們必須好好看一下這個可配置類到底做了什么事情,能讓SpringBoot啟動完成之后也自動初始化好了一個WebMVC的環境。

    點開可以看到這個可配置類上有很多注解修飾,這些注解分別限定在某些條件滿足或不滿足情況下,spring才會初始化并配置這個Config類。關于這些注解和文字解釋如下:

    //聲明為配置類,并且Bean的方法不進行代理 @Configuration(proxyBeanMethods = false)//判斷當前環境是一個SERVLET環境,也就是說是Web環境下這個配置類才生效 @ConditionalOnWebApplication(type = Type.SERVLET)//判斷當前環境是否含有Servlet,DispatcherServlet,WebMvcConfigurer實例(前面SpringMVC章節零XML方式介紹的接口, //類似引入web.xml的Java實例),這些都是WebMVC必不可少的組件,只有這些都存在這個配置類才生效 @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })//判斷當前環境是否存在WebMvcConfigurationSupport,如果不存在這個配置類才生效 //為什么這里不存在WebMvcConfigurationSupport時才能讓配置類生效呢?因為這個接口是一個自定義配置WebMVC的接口, //如果實現了這個接口就意味著開發者自己手動進行了webMVC的配置,那么SpringBoot就不再幫你自動配置了,防止了配置沖突。 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)//自動配置順序:數值越低越優先配置 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//WebMVC配置的核心注解,主要是對DispatcherServlet進行Java配置,以及對任務執行和校驗器進行Java配置。 //當這個配置類生效之后,就會接著進行DispatcherServletAutoConfiguration,TaskExecutionAutoConfiguration, //ValidationAutoConfiguration的配置,重點看DispatcherServletAutoConfiguration @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {..... }

    從上面可以看到,SpringBoot要想自動配置WebMVC環境,那么是需要滿足上述注解定義的一些條件:

    • 處于Web環境下
    • 容器中已經初始化好了WebMVC必須的組件
    • 用戶沒有自己手工配置過WebMVC

    這些條件都滿足后,SpringBoot就會為我們自動配置一個WebMVC環境,但是這個環境是怎么樣的呢(比如采用什么容器,端口號是什么,DispatchServlet怎么配置,Resover怎么配置,Converter怎么配置,等等)?這個就是最后一個注解@AutoConfigureAfter內聲明的來定義了,實際上也就是由DispatcherServletAutoConfiguration.class通過Java零XML配置方式在代碼里為我們提前寫好定義的環境。

    對于核心DispatcherServlet的自動配置,點開這個核心DispatcherServletAutoConfiguration.class:

    可以看到這個DispatcherServlet自動配置類也有很多注解修飾:

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) //如果環境中已經有了用戶自己配置的DispatcherServlet,就不再自動配置 @ConditionalOnClass(DispatcherServlet.class) //在這個DispatcherServlet自動配置之后,通過ServletWebServerFactoryAutoConfiguration對web容器進行配置 @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)

    在這個DispatcherServlet自動配置類中,最核心的就是對DispatcherServlet的配置,可以看到對DispatcherServlet配置的核心代碼在靜態內部類DispatcherServletConfiguration的dispatcherServlet方法上,通過@Bean返回給Spring容器:

    從這個方法內部我們就看出了,SpringBoot自動配置DispatcherServlet時是直接new的,然后設置各種參數最終通過@Bean交給Spring容器。在上面的方法執行結束將一個DispatcherServlet交給Spring容器后,接下來會調用靜態內部類DispatcherServletRegistrationConfiguration的dispatcherServletRegistration方法,對DispatcherServlet的參數進行配置:

    這里設置DispatcherServlet名字為dispatcherServlet;啟動順序是默認值-1(使用時加載);請求接收路徑是/;配置完成后交給Spring容器,這就是對DispatcherServlet的Java代碼配置了。

    不過這里有一個問題,就是這里返回給Spring容器的是一個DispatcherServletRegistrationBean,那么Tomcat等Web容器時怎么將這個DispatcherServletRegistrationBean內包含的DispatcherServlet信息解析出來并生效運行呢?這個知識點有必要做一下說明。

    首先看這個DispatcherServletRegistrationBean的構造是怎么樣的:

    這個DispatcherServletRegistrationBean繼承了ServletRegistrationBean,這個ServletRegistrationBean是攜帶了供Tomcat容器解析加載的DispatcherServlet,讓我們看下這個ServletRegistrationBean的繼承關系圖:

    就可以知道這個DispatcherServletRegistrationBean實際最終是實現了ServletContextInitializer接口,看到這里如果前面對Servlet的SPI有所了解很快就能反應過來,這里是有一個onStartup方法通過SPI機制讓Tomcat啟動時能自動調用到onStartup里面來,通過下面調用鏈最終Spring容器進行了addServlet操作將DispatchServlet添加到Spring容器中并生效,最后進行config方法對DispatcherServlet進行設置:

    關于SPI機制可以參考這篇文章



    但是讀了上面的源碼又引發了兩個很值得深思的問題:

  • 在靜態內部類DispatcherServletConfiguration的dispatcherServlet方法上,通過new一個DispatcherServlet之后直接用@Bean返回給Spring容器了。在學習SpringMVC時我們知道DispatcherServlet需要傳入一個ApplicationContext上下文,如果沒有傳遞則會去默認配置文件解析出一個上下文。但是這里這個DispatcherServlet卻沒有關聯任何Spring上下文的地方卻能使DispatcherServlet起作用,那這個關聯操作到底是在哪里進行的呢?
  • 這需要我們看SpringBoot中DispatcherServlet的源碼是怎么寫的,讓我們看下有參的構造是怎么樣的,因為無參構造只是一個空方法:



    使用IDEAJ的查找工具,看下這個this.webApplicationContext在哪些地方被設置,可以找到是在setApplicationContext方法內進行設置的:

    看到這個重寫方法以及方法注釋,根據Spring的知識我們應該猜想到這個類應該是實現了ApplicationContextAware接口。ApplicationContextAware接口是做什么的呢?這個接口是當Spring容器初始化結束之后,實現了ApplicationContextAware接口的實現類就會被調用并且執行setApplicationContext方法。回到這里也就是說當Spring容器初始化完成之后,由于實現了ApplicationContextAware接口,于是會執行setApplicationContext方法,在這個方法中將初始化完成的Spring上下文賦值給this.webApplicationContext變量(這個變量就是DispatchServlet內部的Spring上下文變量),于是就解釋了為什么SpringBoot在new一個DispatchServlet時不需要傳入Spring上下文的原因。

  • 理解了上面的問題和答案,就引出第二個問題:
  • 前面學習SpringMVC時知道對DispatcherServlet的配置是將Spring容器對象作為參數傳給DispatcherServlet;但是SpringBoot自動配置WebMVC時卻是將DispatcherServlet對象傳給Spring容器;這兩種場景的實現是反過來的,為什么會這樣設計呢?其實這兩種方式的區別就在于它們分別是怎么接在Spring容器的。

    回答為什么SpringBoot是將DispatchServlet傳給Spring容器的問題,就要結合上面第一個問題的解析。這是因為要調用實現了ApplicationContextAware接口的實現類,則必須保證這個實現類在Spring容器當中才能生效。也就是說SpringBoot中實現了ApplicationContextAware接口的DispatchServlet要想觸發接口方法,則必須作為一個Bean存在于Spring容器中,這就是SpringBoot為什么要將DispatchServlet作為Bean傳入到Spring容器中的原因了。

  • 上面介紹完SpringBoot對DispatchServlet進行自動配置的細節,那么還為我們做了哪些組件配置呢?這里要回過來看WebMvcAutoConfiguration
  • 在這個自動配置類中,定義了一個靜態內部類WebMvcAutoConfigurationAdapter,實現了WebMvcConfigurer接口。這個就和之前學習SpringMVC應用時介紹的一樣,通過實現WebMvcConfigurer接口就可以擁有一個類似web.xml功能,可以對其它組件進行配置操作:

    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer

    在這個里面實現了默認的Resolver,MessageConverters,ViewResolver等組件。如果我們要添加自定義的組件怎么辦呢?對于SpringMVC而言我們必須在方法中將這個自定義的組件添加到對應組件集合中,但是SpringBoot對此進行的拓展,使得我們可以直接通過@Bean的方式就可以添加自定義組件(這一點在SpringMVC是做不到自定義添加的):

    五、總結一下SpringBoot除了自動配置DispatchServlet之外,還配置了以下

  • spring boot會默認注入一個視圖解析器:ContentNegotiatingViewResolver
    主要做2個事情:
    • 整合所有的視圖解析器
    • 遍歷所有的視圖解析器選一個最佳的方案

    spring boot 會在當前的spring容器里面找到所有HttpMessageConverter類型的Bean 封裝成一個集合

    • spring boot會自己注入一個默認的實現 jackson
    • 如果用戶需要自己配置的話 只需要@Bean
  • SpringBoot實現一個字符串到日期的參數轉換器:
  • 每當前端要將字符串日期傳遞到后臺之后,要自動轉換成日期格式類型時,可以寫一個Converter進行轉換。


    至此,關于SpringBoot自動配置WebMVC的源碼原理就分享到這里了。

    • 以springmvc自動裝配為例看底層源碼細節:配置類WebMvcAutoConfiguration.
    • 這個配置類有多個注解分別限定在某些條件滿足或不滿足情況下,spring才會初始化并配置這個Config類,
    • 其中注解@AutoConfigureAfter中DispatcherServletAutoConfiguration是自動裝配的核心類,
    • 這里主要對DispatcherServlet進行配置,和servlet被tomcat調用onStartup()一樣,
    • 需要先new出DispatcherServlet并放入Spring容器,然后在tomcat調用前進行addXXX參數的操作,
    • 在springboot實現的javaConfig里,filter,listener,DispatcherServlet,servlet這些servler子類都用XXXRegistrationBean來封裝,
    • 這些XXXRegistrationBean都實現了RegistrationBean超類(這個超類擁有onStartup方法),于是保證這些Bean都可以被 tomcat識別并執行,在執行各種不同類型的Bean時,會調用不同的register或configure方法進行add各自不同參數的方法,
    • 從而實現了各種servlet組件參數的自動配置和加載步驟。對于web容器的配置是ServletWebServerFactoryAutoConfiguration配置類中進行主要對web容器類型IP端口連接數等參數進行配置。

    參考文章

    總結

    以上是生活随笔為你收集整理的从web.xml谈谈SpringMVC集成spring的初始化流程及SpringBoot集成SpringMVC的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。