本文内容来源于博主一次问题排查的过程,最终说明为何不要将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有如下几点: