爲何不要將spring-boot相關依賴打入二方包

 

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

  先介紹一下背景:咱們應用是一個標準的spring+webx工程,博主在一次項目發佈前爲了再次測試一下本身的代碼,將分支部署到平常環境中,可是項目啓動的時候報錯:git

  

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

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

  第二是這個錯誤自己,Could not open ServletContext resource很常見,可是這個錯誤後面一般都是沒法打開一個具體的文件,通常就是工程裏(例如autoconfig)配置了一個文件路徑,而文件路徑不存在。可是,這裏的文件路徑居然是NONE。spring

  •  問題排查及解決過程

  首先是懷疑本身的分支出了什麼問題,部署了一下主幹,還有這個報錯。由於錯誤堆棧來看是從tomcat過來的,所以猜想有如下解釋:sql

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

  排查過程:tomcat

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

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

  分別將線上和預發war包裏的二方包文件列表輸出到兩個不一樣的文件中,而後diff兩個文本後發現了始做俑者:spring-boot

  

  發現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包。
相關文章
相關標籤/搜索