為什么不要將spring-boot相關依賴打入二方包


 

  本文內容來源於博主一次問題排查的過程,最終說明為什么不要將spring-boot相關依賴打入二方包。

  先介紹一下背景:我們應用是一個標准的spring+webx工程,博主在一次項目發布前為了再次測試一下自己的代碼,將分支部署到日常環境中,但是項目啟動的時候報錯:

  

  第一眼看到這個堆棧后有點懵逼

  第一是上一次部署分支還沒問題,距離上次部署自己新增的代碼也很簡單,不可能寫出如此詭異的代碼去改變spring的行為。況且從tomcat啟動日志來看,報錯的時候還根本沒有到應用的代碼。

  第二是這個錯誤本身,Could not open ServletContext resource很常見,但是這個錯誤后面通常都是無法打開一個具體的文件,一般就是工程里(例如autoconfig)配置了一個文件路徑,而文件路徑不存在。但是,這里的文件路徑竟然是NONE。

  •  問題排查及解決過程

  首先是懷疑自己的分支出了什么問題,部署了一下主干,還有這個報錯。因為錯誤堆棧來看是從tomcat過來的,因此猜測有以下解釋:

  1. 環境因素(PE有沒有對tomcat或pandora做什么事情)
  2. 文件不存在或文件權限有問題(導致path為NONE)
  3. Jar包沖突(原二方包版本變化)或Jar包干擾(新增二方包產生干擾)

  排查過程:

  1. 首先我們懷疑的環境因素導致的,對比了一下日常/預發和線上的環境差異,jdk、tomcat、pandora版本都一樣。同時在日常也啟動了一遍,也報這個錯。如果環境因素導致的不會日常和線上都存在問題吧。看了下郵件也沒發現PE有什么動作,這時候還不放心申請了個項目環境跑了一遍還是這個錯,如果日常和預發是PE搞了什么鬼那么項目環境完全是一個干凈的環境,應該不會產生干擾。因此環境因素基本排除掉了。
  2. 日常和預發的WEB-INF文件都是存在的,同時對比了下線上的WEB-INF文件夾的權限,發現也是完全一樣的,因此文件的因素也被排除掉了。
  3. 接下來我們重啟了一台線上機器的war包,沒有任何報錯,這個時候又有點懷疑環境因素,我們將線上機器war包scp到預發機器上,啟動沒有報錯!那么,環境因素可以徹底排除掉了。

  同為主干代碼線上war包沒問題而日常/預發部署就有問題,問題基本清晰起來了,應該是某個SNAPSHOT二方包引起的問題。

  分別將線上和預發war包里的二方包文件列表輸出到兩個不同的文件中,然后diff兩個文本后發現了始作俑者:

  

  發現framework-sqlanalyse-1.0.0-SNAPSHOT包的大小有所變化,並且新增了pandora-boot及spring-boot等一些新的二方包。

  mvn tree查看了下,pandora-boot和spring-boot果然就是由framework-sqlanalyse引入進來的。在pom中把pandora-boot和spring-boot排掉之后再次部署終於成功了。至此這個問題算是解決了,但是到底是怎么產生的呢?

  • 問題定位

  回到剛剛diff的文件結果,我們發現這並不是一個包的沖突(因為只有framework-sqlanalyse這個包變化了),而是新包產生的干擾。也就是說非spring-boot應用引入了spring-boot或pandora-boot的二方包之后就會產生上述問題。那么這個問題到底是如何產生的?

  首先需要定位到底是哪個包導致的這個問題,經過分類排包后定位到是org.springframework.boot:spring-boot-autoconfigure這個包引起的,但是我們的報錯堆棧中並沒有org.springframework.boot相關的類。

  spring-boot-autoconfigure這個包用於spring boot自動配置機制,如果在應用中添加了@EnableAutoConfiguration就會觸發自動配置,它會根據定義在classpath下的類,自動生成一些Bean,並加載到Spring的Context中。spring boot應用啟動類上的@SpringBootApplication便繼承自@EnableAutoConfiguration。

  一開始也懷疑是自動配置導致的,但是我們的應用只是一個spring+webx的普通web應用而已,並沒有@EnableAutoConfiguration,因此不會觸發自動配置,也不會加載embed tomcat。

  后來發現這是來自於spring boot的一個官方issue:https://github.com/spring-projects/spring-boot/issues/5740

  始作俑者是spring-boot-autoconfigure中一個配置類JerseyAutoConfiguration中的內嵌類JerseyWebApplicationInitializer

  @Order(Ordered.HIGHEST_PRECEDENCE)
  public static final class JerseyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
      // We need to switch *off* the Jersey WebApplicationInitializer because it
      // will try and register a ContextLoaderListener which we don't need
      servletContext.setInitParameter("contextConfigLocation", "<NONE>");
    }
  }

  我們知道,繼承了WebApplicationInitializer的類都會被應用加載,原因就在於SpringServletContainerInitializer,他會實例化classpath下所有繼承了WebApplicationInitializer的類,並且會觸發每個WebApplicationInitializer的onStartup方法。這樣,servletContext就被篡改了。

  在啟動日志中看見有這樣的內容:INFO: Spring WebApplicationInitializers detected on classpath: [org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration$JerseyWebApplicationInitializer@155b6f9d]  這也印證了JerseyWebApplicationInitializer確實被加載了。

  當ServletContext初始化完成之后web容器就開始啟動了,我們的應用是基於webx的,配置在web.xml中的webx的監聽器便開始起作用了。

  WebxContextLoaderListener實現了spring的ContextLoaderListener。它會調用ContextLoader的initWebApplicationContext()方法,而在webx中初始化的是WebxComponentContext(繼承自XmlWebApplicationContext)。ContextLoaderListener是使用servletContext來做初始化的,這時已經被修改過了,那個NONE就是這樣被傳過來的。

  •  總結

  至此這個問題已經搞清楚了,最后總結一下上面這個case有以下幾點:

  1. 除非萬不得已,今后線上部署時應該杜絕SNAPSHOT二方包,一方面減少下次部署隱患,另一方面排查問題時也可以排除不必要的干擾。
  2. 不要將spring boot相關依賴打入二方包中,如果webx應用使用了該二方包會必現上述問題,目前spring boot與webx依然是不兼容的。
  3. 在項目工程開發時,spring boot應用的正確依賴姿勢應該是這樣的:根pom中應該將spring-boot及pandora-boot相關依賴放在dependencyManagement標簽中,讓子模塊去顯示依賴,而不要放在dependencies標簽中污染client包。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM