本文內容來源於博主一次問題排查的過程,最終說明爲何不要將spring-boot相關依賴打入二方包。java
先介紹一下背景:咱們應用是一個標準的spring+webx工程,博主在一次項目發佈前爲了再次測試一下本身的代碼,將分支部署到平常環境中,可是項目啓動的時候報錯:git
第一眼看到這個堆棧後有點懵逼github
第一是上一次部署分支還沒問題,距離上次部署本身新增的代碼也很簡單,不可能寫出如此詭異的代碼去改變spring的行爲。何況從tomcat啓動日誌來看,報錯的時候還根本沒有到應用的代碼。web
第二是這個錯誤自己,Could not open ServletContext resource很常見,可是這個錯誤後面一般都是沒法打開一個具體的文件,通常就是工程裏(例如autoconfig)配置了一個文件路徑,而文件路徑不存在。可是,這裏的文件路徑居然是NONE。spring
首先是懷疑本身的分支出了什麼問題,部署了一下主幹,還有這個報錯。由於錯誤堆棧來看是從tomcat過來的,所以猜想有如下解釋:sql
排查過程:tomcat
同爲主幹代碼線上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有如下幾點: