为何不要将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包。
相关文章
相关标签/搜索