<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot項(xiàng)目的兩種打包方式分析

          共 4381字,需瀏覽 9分鐘

           ·

          2021-12-26 20:36

          點(diǎn)擊上方 Java學(xué)習(xí)之道,選擇 設(shè)為星標(biāo)

          每天18:30點(diǎn),干貨準(zhǔn)時(shí)奉上!

          作者: 枕邊書
          來(lái)源: zhenbianshu.github.io

          Part1前言

          最近對(duì) Spring 越來(lái)越感興趣,卻在閱讀它的源碼時(shí)很容易被類之間的跳轉(zhuǎn)和方法的嵌套繞暈,為了避免無(wú)盡的煩惱,我決定跟它做一個(gè)了斷,不再追求細(xì)節(jié),了解其啟動(dòng)過(guò)程和重要組件即可,之后遇到細(xì)節(jié)問(wèn)題再看對(duì)應(yīng)模塊的源碼。

          我們都知道,一個(gè) Java Web 服務(wù)進(jìn)程,Web 服務(wù)器是其必不可少的組件之一,僅有 Spring 是無(wú)法受理系統(tǒng)的 HTTP 請(qǐng)求的。而且在 Java 的 Servlet 模型里,Spring 是作為 Web 服務(wù)器里的一個(gè) Servlet 存在的,這就更能說(shuō)明 Spring 和 Web 服務(wù)器的關(guān)系之親密了。

          但每個(gè) Java 進(jìn)程都只有一個(gè)主類,進(jìn)程從其靜態(tài)的 main 函數(shù)里啟動(dòng),那么在 Spring 和 Web 服務(wù)器配合的場(chǎng)景中,Spring 和 Web 服務(wù)器之間是誰(shuí)先啟動(dòng)的呢?Spring 容器和 Servlet 容器之間有包含關(guān)系嗎?

          要回答這兩個(gè)問(wèn)題,還需要分場(chǎng)景來(lái)看。

          由于 Java Web 服務(wù)器里我們最熟悉 Tomcat,后面就以 Tomcat 來(lái)代表 Web 服務(wù)器了,而 Spring 相關(guān)我們都基于 SpringBoot 來(lái)說(shuō)。

          Part2Jar 包方式

          實(shí)例

          我們最常看到的是下面例子中的寫法。

          @SpringBootApplication
          public?class?ServerStarter?{
          ????public?static?void?main(String[]?args)?{
          ????????SpringApplication.run(ServerStarter.class,?args);
          ????}
          }

          這種方式非常符合我們對(duì) Java 進(jìn)程啟動(dòng)的認(rèn)知。可以看到,我們?cè)?ServerStarter.main 方法里直接使用了 SpringApplication.run() 方法,而 Spring 啟動(dòng)的流程都在 SpringApplication 類內(nèi),我們后續(xù)再詳細(xì)分析。

          在 IDE 里我們可以直接執(zhí)行這個(gè)主類來(lái)啟動(dòng) Spring 應(yīng)用,而脫離了 IDE,我們就只能把整個(gè)應(yīng)用打成一個(gè) fat jar,并且在 jar 包的 MANIFEST 文件內(nèi)設(shè)置 Main-Class 屬性值為 package.path.ServerStarter 來(lái)聲明主類。這樣,在使用 java -jar spring_starter.jar 時(shí), JVM 就知道該從哪個(gè)類開始加載。

          聯(lián)系

          Spring 容器是啟動(dòng)成功后, Tomcat Web 服務(wù)器的啟動(dòng)就要靠 Spring 了,我查看了 SpringApplication 類的代碼整理出以下流程。

          1. 創(chuàng)建 SpringApplication 時(shí)使用在 deduceWebApplicationType() 通過(guò)一些標(biāo)志類(如 org.springframework.web.servlet.DispatcherServlet) 是否存在,推斷是出應(yīng)用類型,一般是 WebApplicationType.SERVLET
          2. 創(chuàng)建 createApplicationContext() 方法內(nèi),根據(jù)應(yīng)用類型創(chuàng)建出一個(gè) AnnotationConfigServletWebServerApplicationContext 類型的 ConfigurableApplicationContext。
          3. 由于 AnnotationConfigServletWebServerApplicationContext 類是 ServletWebServerApplicationContext 的子類,在 ApplicationContext.refresh() 時(shí),會(huì)通過(guò) ServletWebServerApplicationContext.onRefresh() 方法創(chuàng)建一個(gè) webServer。
          4. DispatcherServlet 通過(guò) ServletContextInitializer -> ServletRegistrationBean 注冊(cè)到 Tomcat 容器內(nèi),完成兩者的關(guān)聯(lián)。

          Part3War 包方式

          實(shí)例

          在生產(chǎn)環(huán)境部署服務(wù)時(shí),我們更多地使用 war 包的方式,因?yàn)樗梢院芊奖愕刂С?jsp,而通過(guò) jsp 我們可以給生產(chǎn)環(huán)境調(diào)試添加一定的靈活性。在使用 docker 部署時(shí),也能將 Tomcat 和服務(wù)分層,便于鏡像的維護(hù)。

          我們知道,一個(gè) war 包是沒(méi)有自運(yùn)行能力的,必須要先啟動(dòng)一個(gè) Tomcat 進(jìn)程,由 Tomcat 自動(dòng)解壓并加載 webapps 目錄下的 war 包來(lái)啟動(dòng)服務(wù),所以通過(guò) war 包啟動(dòng)的 Spring Web 主類一定是 Tomcat 類。

          在主類已存在的情況下,我們的 Spring 入口類就不再需要 main 方法了,常見寫法如下:

          @SpringBootApplication
          public?class?ServerStarter?extends?SpringBootServletInitializer?{
          ????@Override
          ????protected?SpringApplicationBuilder?configure(SpringApplicationBuilder?application)?{
          ????????return?application.sources(ServerStarter.class);
          ????}
          }

          可以看到,Spring 入口類繼承了 SpringBootServletInitializer 類,最上層的接口是 org.springframework.web;.WebApplicationInitializer

          聯(lián)系

          查看 Tomcat 的 web.xml 配置,沒(méi)有一絲 spring 的痕跡,要知道 Tomcat 是如何聯(lián)系上 Spring 的,還要從 WebApplicationInitializer 接口開始。

          這種實(shí)現(xiàn)方式需要兩種新特性的支持:

          • Java 1.6 之后新添加了一個(gè)類 java.util.ServiceLoader,提供了一種新的有別于 ClassLoader 的類發(fā)現(xiàn)機(jī)制。當(dāng)我們?cè)?META-INF/services 文件夾內(nèi)添加文件,文件名是全路徑的接口,文件內(nèi)容每一行是全路徑的接口實(shí)現(xiàn),就可以通過(guò) ServiceLoader.load(Class interface) 方法加載到對(duì)應(yīng)的接口實(shí)現(xiàn),這種機(jī)制就是 SPI (Service Provider Interface),使用這種機(jī)制,加載用戶實(shí)現(xiàn)的特定接口時(shí)就不需要類加載器掃描所有的類文件了。不過(guò)如果要加載的接口是 Java 底層類時(shí),類加載器會(huì)是 BootstrapClassLoader 或 ExtClassLoader,加載子類時(shí)需要指定使用 classLoader 為 currentThreadClassLoader (一般是加載應(yīng)用的 AppClassLoader)。
          • Servlet 3.0+ 提供了另一種替代 web.xml 的 Servlet 配置機(jī)制 ServletContainerInitializer 接口,Tomcat 會(huì)在 Servlet 容器創(chuàng)建后調(diào)用 ServletContainerInitializer.onStartup() 方法,便于我們向容器內(nèi)注冊(cè)一些 Servlet 對(duì)象。

          我一路跟隨文檔向上查看,總結(jié)了一下整個(gè)流程。

          1. Spring 在 web 包的 META-INF/services 內(nèi)添加了文件 javax.servlet.ServletContainerInitializer,內(nèi)容為 org.springframework.web.SpringServletContainerInitializer。
          2. Tomcat 容器啟動(dòng)后,通過(guò) ServiceLoader 加載到 SpringServletContainerInitializer 實(shí)例。
          3. SpringServletContainerInitializer 通過(guò) @HandlesTypes 注解獲取到 WebApplicationInitializer 接口的所有實(shí)現(xiàn)(包括 SpringBootServletInitializer、AbstractDispatcherServletInitializer 等)。
          4. SpringBootServletInitializer.onStartup() 方法內(nèi)初始化了 Spring 容器。
          5. AbstractDispatcherServletInitializer.registerDispatcherServlet() 方法內(nèi)實(shí)現(xiàn)了 DispatcherServlet 與 Tomcat 的關(guān)聯(lián)。

          Part4小結(jié)

          看完了兩種服務(wù)打包方式下 Spring 容器被加載的過(guò)程,文章開頭的兩個(gè)問(wèn)題應(yīng)該就有跡可循了。

          首先 Spring 和 Web 服務(wù)器的啟動(dòng)先后會(huì)根據(jù)服務(wù)打包方式有所不同,使用 jar 包時(shí)是 Spring 先啟動(dòng),而使用 war 包時(shí)是 Web 服務(wù)器先啟動(dòng)。

          而 Spring 容器與 Servlet 容器的包含關(guān)系,我理解是并不存在的,A 啟動(dòng)了 B,所以 A 包含 B 的理論由上文也可以看出是站不住腳的。Servlet 容器和 Spring 之間只是存儲(chǔ)的元素不同,Servlet 容器內(nèi)存放著 Servlet 實(shí)例,而 Spring 中存放著各種環(huán)境變量、Bean 對(duì)象等。而它們之前的聯(lián)系就是 DispatcherServlet,它既是一個(gè) Servlet 實(shí)例,又是一個(gè) Bean,通過(guò) DispatcherServlet,Tomcat 可以調(diào)用到 Spring 容器內(nèi)部的對(duì)象,從線程棧上來(lái)看是 Web 服務(wù)器在下,Spring 更往上。

          -- END?--

          -??| 更多精彩文章 -



          加我微信,交個(gè)朋友
          長(zhǎng)按/掃碼添加↑↑↑

          瀏覽 102
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九色在线视频 | 操碰在线中文字幕 | 无码插入大大大大 | 亚洲成人777 | 国产三级电影在线观看 |