二方包依赖spring boot引发的坑
问题描述
应用使用的框架基于spring+webX,代码迭代之后,启动报错。
问题定位
问题比较
- 网上搜索相关报错信息,大多数都是提示该类错误:
Could not open ServletContext resource [/WEB-INF/applicationContext.xml]
一般错误的原因都在该目录下找不到applicationContext.xml文件,在web.xml文件中配置一下就行。
- 但是本文遇到的问题是:
Could not open ServletContext resource [/
]
问题解决过程
- 代码上基本上没有改动框架的配置,都是增加的业务逻辑,初步定位是maven依赖的问题
- 从master拉新分支(能启动成功),然后将改动之后的maven依赖同步过来,果然,项目启动报了相同的错误。
- 通过mvn dependency:tree命令,输出不报错分支依赖树和报错分支的依赖树,经过对比,发现在报错的分支中多了和spring-boot相关的启动jar包。
- 排掉和spring启动相关的包,重新部署,项目启动成功。
细化分析
为什么在非spring boot项目中依赖spring boot包,启动报错?
上诉我们只是将spring boot相关包排掉,但是不清楚具体是哪个jar包造成的影响。
经过分类排包后定位到是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:
始作俑者是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就被篡改了。
在启动日志中看见有这样的内容(下图):这也印证了JerseyWebApplicationInitializer确实被加载了。
当ServletContext初始化完成之后web容器就开始启动了,我们的应用是基于webx的,配置在web.xml中的webx的监听器便开始起作用了。
WebxContextLoaderListener实现了spring的ContextLoaderListener。它会调用ContextLoader的initWebApplicationContext()方法,而在webx中初始化的是WebxComponentContext(继承自XmlWebApplicationContext)。ContextLoaderListener是使用servletContext来做初始化的,这时已经被修改过了,那个NONE就是这样被传过来的。
总结
- 采用最小化原则,不要在二方包中依赖spring boot启动相关的jar包,(spring boot与webx不兼容的)
- 应该将相关依赖放在根pom的dependencyManagement标签中,让子模块去显示依赖,而不要放在dependencies标签中直接定义
- 线上部署时应该杜绝SNAPSHOT二方包,采用RELEASE