久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    深入分析SpringBoot源碼如何內嵌Tomcat容器?

     萬皇之皇 2019-08-08

    作者:陌北有棵樹,Java人,架構師社區合伙人!

    【一】總述

    SpringBoot的誕生,極大的簡化了Spring框架的使用過程,提升了開發效率,可以把它理解為一個整合包,使用了SpringBoot,就可以不用自己去進行繁瑣的配置,通過幾個簡單的注解,就可以構建一個基于REST的服務。同時,SpringBoot的快速構建部署的特性,為當下大熱的微服務落地提供了極大的便利,可以說是構建微服務的理想框架。

    歸納來說SpringBoot的特性有如下幾點:

    1. 自動配置

    2. 內置tomcat、jetty、undertow 三大web容器

    3. 將Web應用打成jar包啟動

    那么SpringBoot是怎樣做到上述三個特性的呢?是我接下來的研究方向,本篇主要研究的是后兩個特點,如何內嵌了Web容易,將應用打成jar包,怎么還能像Web程序一樣運行。

    本文是筆者患難與共的好兄弟Dewey Ding及筆者Debug了若干天的成果,謹以此篇留作紀念。

    【二】問題引出和總體思路

    • 按照常規的Web容器的啟動方式,明顯是無法和SpringBoot這種jar包的運行方式兼容的,那么他們之間是如何做到無縫銜接的合作呢?

    • 最終運行的依然是SpringMVC框架,那么SpringBoot又是如何做到在內置了Tomcat的同時,又和SpringMVC無縫銜接的呢?

    • 綜上所述,整個系列需要研究的技術點如下(本文并未完全覆蓋):

      1. SpringBoot啟動Tomcat

      2. SpringMVC初始化DispatcherServlet

      3. Tomcat的兩種啟動方式

      4. Tomcat在SpringBoot中如何拿到配置

    【三】SpringBoot啟動過程具體分析

    在SpringBoot中,一個Web應用從啟動到接收請求,我粗略將它分為四步:

    1. SpringBoot初始化

      • 初始化SpringApplication:包括環境變量、資源、構造器、監聽器

      • 開始啟動:啟動監聽(listeners)、加載配置(environment)、創建上下文(applicationContext)

      • 自動化配置:這個部分等到后面單獨研究

    2. Tomcat初始化

    3. Tomcat接收請求

    4. SpringMVC初始化

    這一部分的學習真可謂一波三折,每次Debug SpringApplication的run方法,都會迷失在茫茫的源碼之中,看書和博客也都是云里霧里,所以這次決定換一種學法,先從宏觀上了解都要做什么,至于具體細節,等到需要的時候再去分析。比如今天要了解的是和Tomcat啟動相關的部分,那么就先只了解這個模塊。

    關于SpringBoot和Tomcat是如何合作的,在實際Debug之前,我們先拋出如下幾個問題:

    • SpringBoot有main()方法,可以直接打成jar包運行;SpringMVC沒有main方法,所以無法自己啟動,要依賴Tomcat,Tomcat本質是個容器,所以Tomcat中一定有main方法或者線程的start()方法來啟動Spring程序

    • /WEB-INF是Web應用需要的,SpringMVC配置了這個,是為了Tomcat讀取,從而啟動Web容器

    • 項目要部署到webapp目錄下,才能被Tomcat運行

    • 那么問題來了,SpringBoot沒有做這些配置,是怎么做到內置Tomcat容器,并讓Tomcat啟動的呢?

    【SpringBoot和Tomcat的初始化】

    我們先來看Tomcat的啟動時在SpringBoot啟動的哪一步?這里只列舉比較關鍵的幾步:

    1. SpringApplication的run方法

      org.springframework.boot.SpringApplication#run(java.lang.String...)
    2. 刷新IOC容器(Bean的實例化)

      org.springframework.boot.SpringApplication#refreshContext()
      org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh()
    3. 創建WebServer

      在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh調用了createWebServer

      org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer()
      private void createWebServer() {
      WebServer webServer = this.webServer;
      ServletContext servletContext = getServletContext();
      if (webServer == null && servletContext == null) {
      // 重點:這時初始化了dispatcherServlet
      ServletWebServerFactory factory = getWebServerFactory();
      // 創建
      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();
      }

      這里就是Tomcat的創建和啟動過程了,關于下面這兩行代碼中,蘊含著我們尚未去發現的秘密,后面會繼續分析(蘊含著的秘密真的坑苦了我們,竟然與它擦肩而過,然后繞了一大圈才回來,淚奔中~~):

      this.webServer = factory.getWebServer(getSelfInitializer());
      ······
      getSelfInitializer().onStartup(servletContext);

      在這里創建了Tomcat,Connector,Host,Engine并且設置一些屬性,關于Tomcat的具體內容,由于內容比較多,就不在此篇詳細展開,后續會專門研究。這里我們發現,在Tomcat啟動的時候,servletClass還沒有獲取到dispatcherServlet,而在第一次收到請求時,servletClass就變成了dispatcherServlet,這里就對我們形成了一定誤導,以為是第一次收到請求時Tomcat才加載了默認的wrapper,后來才發現出現了偏差,經歷了無數次斷點后,才回到正確的路上。


    4. 這里不得不提到,一個最開始被我們忽略,后來才發現是個重要的地方:getSelfInitializer() 。關于SpringBoot是如何把“/”和“dispatcherServlet”的關聯給到Tomcat這件事,就蘊含在下面這段代碼之中:

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

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

      //注意 ServletContextInitializerBeans()
      protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
      return new ServletContextInitializerBeans(getBeanFactory());
      }

      // 注意 this.initializers 和 addServletContextInitializerBeans
      public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
      Class<? extends ServletContextInitializer>... initializerTypes) {
      this.initializers = new LinkedMultiValueMap<>();
      this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
      : Collections.singletonList(ServletContextInitializer.class);
      addServletContextInitializerBeans(beanFactory);
      addAdaptableBeans(beanFactory);
      List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
      .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
      .collect(Collectors.toList());
      this.sortedList = Collections.unmodifiableList(sortedInitializers);
      logMappings(this.initializers);
      }

      讓我們來看兩張對比圖:


    5. 通過上面兩個Debug的斷點圖,我們可以看到,在執行了addServletContextInitializerBeans(beanFactory)和addAdaptableBeans(beanFactory)方法之后,this.initializers的賦值發生了變化,兩個Servlet,四個Filter都被賦到里面,至于這兩個方法中的處理邏輯,此時已經心累到不想去看,暫記jira。

      接下來的onStartup()方法,調用了接口org.springframework.boot.web.servlet.ServletContextInitializeronStartup()方法。

    這里有下面幾點需要注意:

    • SpringBoot創建Tomcat時,會先創建一個根上下文,webapplicationcontext傳給tomcat

    • 啟動web容器,要先getWebserver,會創建tomcat的Webserver - 這里會把根上下文作為參數給org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer,這里和tomcat的context進行merge

    • 初始化servletcontext - 會把root上下文放進去,后面初始化dispatcherServlet時,會通過servletcontext拿到根上下文

    • 啟動tomcat:調用Tomcat中Host、Engine的啟動方法

    【DIspatcherServlet的初始化】

    初始化DispatcherServlet,是在第一次發起Web請求的時候(AbstractAnnotationConfigDispatcherServletInitializer)

    • AbstractAnnotationConfigDispatcherServletInitializer

      1. 初始化ContextLoaderListener:創建ApplicationContext(根上下文)

      2. 初始化DispatcherServlet - 這時需要用ApplicationContext

      3. ApplicationContext是ServletContext的父上下文(WebApplicationContext是Spring為web應用提供的接口) 

      4. 可以創建多個DispatcherServlet,每個可以創建自己內部的子上下文(每個Servlet都會有一個上下文)

      5. DispatcherServlet建立上下文是為了持有Spring MVC的Bean對象

    具體調用過程如下:

    • javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)

      • GenericServlet實現了Servlet接口

      • 這個方法啟動SpringBoot時會調用一次,初始化dispatcherServlet時還會調用一次,不過兩次的config中的serviceClass不一樣,第二次是dispatcherServlet

    • org.springframework.web.servlet.HttpServletBean#init

    • org.springframework.web.servlet.FrameworkServlet#initServletBean

      protected WebApplicationContext initWebApplicationContext() {
      WebApplicationContext rootContext =
      WebApplicationContextUtils.getWebApplicationContext(getServletContext());
      WebApplicationContext wac = null;

      if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      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, 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
      wac = findWebApplicationContext();
      }
      if (wac == null) {
      // 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) {
      //重要的刷新上下文操作
      onRefresh(wac);
      }
      }

      if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
      }

      return wac;
      }
      • 獲取根上下文(這時根上下文已經在SpringBoot啟動時初始化好了)

      • 然后把根上下文給webApplicationContext

      • org.springframework.web.servlet.DispatcherServlet#onRefresh - 這時就要刷新子上下文了,刷新上下文要刷新HandlerMapping,HandlerAdapter這些

        protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
        }

        這里記錄幾個重要的時間點:


      • 這時會打印“Initializing Servlet 'dispatcherServlet'”日志,

      • org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext

      • 這部分比較重要,附上代碼:

    【四】SpringMVC和SpringBoot在啟動過程中不同點歸納

    關于SpringMVC、SpringBoot與Tomcat合作中,啟動方式不同點的總結如下

    • SpringMVC

      1. 先啟動tomcat,tomcat會去找web.xml,找到的是DispatcherServlet

      2. 初始化DispatcherServlet的上下文,要從ServletContext中找根上下文,這時是沒有的

      3. 創建根上下文

    • Springboot

      1. 在啟動過程中,SpringBoot會將DispatcherServlet給到Tomcat的Service的defaultWrapper中(如何給到的后面會說明)

      2. Tomcat啟動時,就會把“/”路徑和DispatcherServlet匹配

      3. 當第一次發起Web請求時,初始化DispatcherServlet,包括HandlerMapping,HandlerAdapter

      本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發現有害或侵權內容,請點擊一鍵舉報。
      轉藏 分享 獻花(0

      0條評論

      發表

      請遵守用戶 評論公約

      類似文章 更多

      主站蜘蛛池模板: 欧美精品一区二区在线观看播放| 免费人成网站免费看视频| 少妇无套内射中出视频| 无码人妻一区二区三区兔费| 国产成人无码A区在线观看视频 | 亚洲一区成人在线视频| 人妻中出无码中字在线| 国产对白老熟女正在播放| 国产精品国语对白露脸在线播放| 亚洲午夜久久久久久久久电影网| 日本一道一区二区视频| 久久夜色撩人精品国产小说| 成人午夜大片免费看爽爽爽 | 狠狠综合久久AV一区二区| 十八禁午夜福利免费网站| 成人免费看片又大又黄| 亚洲精品无码成人A片九色播放| 免费观看一区二区三区| 少妇高潮水多太爽了动态图| 人人妻人人狠人人爽| 影音先锋2020色资源网| 亚洲欧洲精品日韩av| 精品国产精品国产偷麻豆| 亚洲欧美综合在线天堂| 久久婷婷五月综合97色直播| 青草青草久热精品视频在线观看 | 女人的天堂A国产在线观看| 亚洲精品无码久久久久SM| 秋霞A级毛片在线看| 亚洲一区二区精品偷拍| 久久精品国产亚洲AV忘忧草18| 夜夜爱夜鲁夜鲁很鲁| 免费无码AV一区二区波多野结衣| 欧美成人家庭影院| 三上悠亚久久精品| 欧美怡春院一区二区三区 | 最近中文字幕日韩有码| 色综合久久久久综合体桃花网| 国产精品自在拍首页视频| 中文字幕有码高清日韩| 性刺激的欧美三级视频中文字幕 |