太长不看(TLNR)的直接看总结就好
现象
前几天服务(commonservice)上线的时候,疯狂报NPE,看了下代码似乎是一个不可能抛异常的地方,尤其是在本地运行正常的情况下就更显诡异了。找了下银泉帮忙定位到问题,日志的切面连接点有点问题,但是还是不清楚触发原因,而且也不能解释为啥本地正常。折腾了半天IDEA,发现原来是引入的jar版本不正确,其实本地也是有问题的。这种问题似乎是IDEA一贯尿性了,只是在这里突然出现又浪费了几个小时。忍不了了,遂升级IDEA到最新版本,目前来看,感觉良好,pom文件有变动的时候会有一个Sync窗口,可以直观地了解jar包导入情况,也没有再出现版本错误的问题(当然,这本来就不是必现的问题)。废话少说,定位到了问题现象之后,接下来就要找到它的根源了。
根源
对于没有啥思路的问题,只能通过排除法就溯源了。首先找到问题引入的时间,通过测试了几轮不同版本的服务代码,确定了问题引入的代码提交记录。然后看下提交记录,大部分是业务代码,只是升级了一个jar包,但是这是个soa自动生成的api啊,能有啥问题?绞尽脑汁也百思不得其解。还是先确认一下问题根源吧,于是在另一个服务(vendor)也引入了这个jar包,这个服务更诡异,直接起不来了。。所以基本上可以确定这个jar包有问题。但这也正是最令人费解的地方,因为这就是个刚刚从soa后台生成下载的还热乎着的api jar包。vendor启动之后报了一些错,可以通过一些“奇淫巧技”解决,银泉就根据报错解决了问题然后启动了,但不确定还有没有问题(我感觉大概率是有的),而且也并没有解释为啥这个包“有毒”。
由于要上堡垒测试,暂时先把这个jar回退了,涉及到的新功能也注释掉了,好在影响的不多,先推进需求要紧。有空的时候,再来看看这个问题。
吃完晚饭之后,测试都结束了,这个问题还是要解决的。先升级jar包看看问题还在不。。md,还在。要不,对比下启动日志?虽然看起来差不多,但也许有些许区别呢?使用beyond compare对比日志后发现,正常的版本会多一条日志
[03:16:46.109] [INFO] [main] - Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfigurationEnhancerBySpringCGLIBEnhancerBySpringCGLIBEnhancerBySpringCGLIB51de14ac] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
有眉目了,再去vendor确认一下,也是同样的区别。但是这个类正常的和异常的服务都有,这他么怎么看。不过,既然这个类都有,那是不是有其他类(或者jar)的区别,因为这个类是个auto-configure相关的类,在有某些类或者jar的情况下会自动加载。说明依赖是有差别的(之前没往这上边想是因为所有的api依赖都是一样的,按道理不应该,而且这个jar的老版本依赖很少(其实不然))。那就再对比一下运行时加载的jar包吧。写了段测试代码
public static void main(String[] args) throws IOException {
File file7 = new File("D:\Users\wang.wei\Documents\task\8月\71.txt");
File file10 = new File("D:\Users\wang.wei\Documents\task\8月\101.txt");
Pattern p = Pattern.compile("-classpath "(.*)"");
String libs7 = Files.readFirstLine(file7, Charsets.UTF_8);
Matcher m7 = p.matcher(libs7);
m7.find();
libs7 = m7.group(1);
Set set7 = Sets.newHashSet(Splitter.on(";").omitEmptyStrings().trimResults().split(libs7));
String libs10 = Files.readFirstLine(file10, Charsets.UTF_8);
Matcher m10 = p.matcher(libs10);
m10.find();
libs10 = m10.group(1);
Set set10 = Sets.newHashSet(Splitter.on(";").omitEmptyStrings().trimResults().split(libs10));
Collection c710 = CollectionUtils.subtract(set7, set10);
Collection c107 = CollectionUtils.subtract(set10, set7);
System.out.println(c710);
System.out.println(c107);
}
结果如下:
[D:Userswang.wei.m2repositoryorgspringframeworkbootspring-boot-starter-jdbc1.4.3.RELEASEspring-boot-starter-jdbc-1.4.3.RELEASE.jar, D:Userswang.wei.m2repositoryorgspringframeworkbootspring-boot-starter-aop1.4.3.RELEASEspring-boot-starter-aop-1.4.3.RELEASE.jar, D:Userswang.wei.m2repositoryorgspringframeworkspring-jdbc4.3.4.RELEASEspring-jdbc-4.3.4.RELEASE.jar, D:Userswang.wei.m2repositorycomctriptrainordercentreoutboundcall_service_client1.0.7outboundcall_service_client-1.0.7.jar]
2. 异常版本-正常版本
[D:Userswang.wei.m2repositorycomctriptrainordercentreoutboundcall_service_client1.0.10outboundcall_service_client-1.0.10.jar]
一目了然了,我他么走了多少弯路。。。
正常版本比异常版本多了几个jar,而这几个jar的差异是因为outboundcall_service_client
的版本不一样,仔细观察了下outboundcall_service_client-1.0.7.jar
的pom文件,发现它竟然有个parent,而这个parent里引入了很多依赖,其中就包括上述的差异jar包(不建议在父pom中直接引入依赖,而应该声明为dependencyManagement)
接下来的事就简单了,在service模块补上spring-boot-starter-aop
依赖,在dal模块补上spring-boot-starter-jdbc
依赖,妥了!
总结
由于现在绝大部分项目都依赖很多公共组件,而公共组件又依赖很多第三方jar包,导致我们起一个项目几乎不用显式声明依赖,久而久之对于项目所真正需要依赖的jar包已经没什么概念了,反正只要服务起来了就好,大多数情况下确实服务能起来就能保证不会出啥问题,但这次的“血案”告诉我们,还是需要严肃对待jar包依赖这个问题。当然,这次的问题主要是升级的api jar包的老版本在父pom里强制声明很多依赖,而升级之后这些依赖没了,导致我们缺失了一些依赖。在父pom应尽量少声明依赖,需要统一管理版本的应该声明为dependencyManagement,正是因为他们依赖了
之前不必要的jar包,才使我们没有在第一时间发现问题。
我已将tieyouflightcommon
依赖的outboundcall_service_client
升级为1.0.10,强烈建议各位依赖此版本启动下服务,这个版本没有对于spring-boot-starter-aop
和spring-boot-starter-jdbc
的依赖,所以如果之前的服务是通过依赖tieyouflightcommon
依赖outboundcall_service_client
从而获得这两个jar包的依赖的话,建议显式在自己的项目声明,毕竟这两个jar包并非需要强制依赖(spring-boot相关的依赖只在spring-boot服务才需要,而tieyouflightcommon
是适用于所有spring应用的)。
outboundcall_service_client
是推送相关的api,这次升级是因为旧的版本不支持跳转落地页,没想到升级出事来了。。记录一下希望后续其他同学需要升级此api的时候不要踩到相同的坑了。