本文内容来源于博主一次问题排查的过程,最终说明SNAPSHOT包的危害性。
先介绍一下背景:我们应用是一个标准的spring+webx工程,博主在一次项目发布前为了再次测试一下自己的代码,将分支部署到日常环境中,但是项目启动的时候报错:
第一眼看到这个堆栈后有点懵逼
第一是上一次部署分支还没问题,距离上次部署自己新增的代码也很简单,不可能写出如此诡异的代码去改变spring的行为。况且从tomcat启动日志来看,报错的时候还根本没有到应用的代码。
第二是这个错误本身,Could not open ServletContext resource很常见,但是这个错误后面通常都是无法打开一个具体的文件,一般就是工程里(例如autoconfig)配置了一个文件路径,而文件路径不存在。但是,这里的文件路径竟然是NONE。
首先是怀疑自己的分支出了什么问题,部署了一下主干,还有这个报错。因为错误堆栈来看是从tomcat过来的,因此猜测有以下解释:
排查过程:
同为主干代码线上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有以下几点:
原文:http://www.cnblogs.com/cishengchongyan/p/6235439.html