Java依赖冲突高效解决之道

Posted 阿里技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java依赖冲突高效解决之道相关的知识,希望对你有一定的参考价值。


   at testing.Test.main(Test.java:23)   at testing.User.getUserId(Test.java:41)   at testing.User.<clinit>(Test.java:35)   ... 1 more


at testing.Test.main(Test.java:19)

at org.springframework.context.annotation.AnnotationConfigUtils.registerAnnotationConfigProcessors(AnnotationConfigUtils.java:190) at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.registerComponents(ComponentScanBeanDefinitionParser.java:150) at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:86) at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:73)

首先需确认JVM中当前加载的缺失方法类,如上"org.springframework.beans.factory.support.DefaultListableBeanFactory"类到底来自哪个jar包,目前最高效的办法:

外部环境容器下,或者某些容器版本过低不支持Arthas在线诊断的情况下,可以通过在JVM启动参数中增加" -XX:+TraceClassLoading",然后重新启动系统,在系统工程日志中即可看到JVM加载类的信息。从中即可找到JVM是从哪个jar包中加载的。

STEP2、在IDEA中(快捷键Ctrl+N)查找异常栈中提示缺失的类在哪些版本的jar包中有,如下图所示:


然后依次查看各版本jar包中冲突类的源码,工程中部分jar打包时附带了源码包可直接看到源码,不带源码的需要用IDEA插件(推荐jad)反编译一下。然后依次搜寻各个jar包中的冲突类,搜寻第一步是点击上图中某个版本类,在IDEA中查找类级次关系(快捷键Ctrl+H),如下图所示:


然后在冲突类及所有冲突类的父类源码中找到NoSuchMethodError异常信息中描述缺失的方法,以上例子中就是"getDependencyComparator()Ljava/util/Comparator"。

上例中通过搜寻可以发现spring-beans-3.2.1.RELEASE.jar,spring-2.5.6.SEC03.jar两个版本DefaultListableBeanFactory类及父类中没有"getDependencyComparator()Ljava/util/Comparator"方法,spring-beans-4.2.4.RELEASE.jar,spring-beans-4.3.5.RELEASE.jar两个版本DefaultListableBeanFactory类中有缺失的"getDependencyComparator()Ljava/util/Comparator"方法。

STEP3、查看应用部署机器上应用lib包目录下(一般是/home/admin/union-uc/target/$projectName/lib或union-pub/target/$projectName.war/WEB-INF/lib)下,找到相关jar包的版本,如上例中:


致此定位问题根本原因是应用启动时加载"org.springframework.beans.factory.support.DefaultListableBeanFactory"类未加载到运行时预期所需的spring-beans-4.3.5.RELEASE.jar版本,而是加载了spring-2.5.6.SEC03.jar导致。

按照以上流程步骤,基本99%的依赖冲突都可以定位到根本原因。定位到原因后如何解决冲突呢?事实上有些时候解决冲突远没有内网上很多帖子描述的"mvn dependency:tree"一下,排排jar那么简单。具体细节请继续看下一章节。

四  通过maven调整依赖jar解决依赖冲突


1  升降级jar包解决依赖冲突


上一章节中的第一个例子中,最简单的情况,如果发生冲突的jar包高版本是完全兼容低版本功能的情况下,只需在pom中简单升级jar包版本即可。

但如果冲突 jar包高版本不兼容低版本,且应用依赖不是很复杂的情况下,可以分析升级冲突jar包后会对哪些业务有影响,具体做法推荐通过IDEA Maven Helper 插件查找冲突jar包有哪些业务依赖(此处不推荐"mvn dependency:tree",目前本人见过的大部分Maven工程都有多个Module,比如*-dal,*-Service,*-Controller,这类工程结构如果module未单独打包上传Maven仓库,"mvn dependency:tree"是不能完整分析依赖关系的),记录下来。如下图所示:


然后升级冲突包,通过回归测试受到影响的二方库对应的业务点。

如果应用依赖非常复杂(例如冲突包有几十个二方库依赖,或者依赖冲突包的二方库是个基础包,业务系统中无法清晰枚举出使用受影响二方库的业务点),这种情况下,如果要通过升级jar包解决依赖冲突,必须完整回归整个应用功能。笔者有几次因为回归不全面引发故障的惨痛经历,希望大家不要重蹈覆辙。通过这几次事例,笔者深刻理解到我们这个时代最伟大的计算机科学家Dijkstra大神“简单是可靠的先决条件”这句至理名言,深深的体会到如果一个系统复杂到你完全无法理清楚他错综复杂的依赖关系的时候,那说明你该重构你的系统了,否则系统维护将会逐步变成噩梦。

当然不是所有情况都可以通过升降级jar解决冲突,举个例子:



如上图假设应用系统同时依赖A.jar,B.jar,而A.jar,B.jar都依赖protobuf-java,系统运行时都会分别用到A.jar,B.jar中protobuf部分的功能,而且A.jar,B.jar依赖的protobuf版本无法通过升高降低版本调整到一致。由于protobuf-java3.0版本序列化协议,类内容各方面都不兼容protobuf-java2.0版本。这种情况无论如何调整依赖都无法解决冲突的问题,要解决这种冲突,请继续往下看,第五第六章内容。

2  排除jar包解决依赖冲突


上一章节中第二个例子,主要原因是容器启动时加载到的类不是预期spring-beans-4.3.5.RELEASE.jar中的类,而是spring-2.5.6.SEC03.jar包中的类,如果spring-2.5.6.SEC03.jar排除对业务无影响,可以通过排除spring-2.5.6.SEC03.jar来解决冲突。与上一节例子类似,可以通过IDEA Maven Helper 插件确定spring-2.5.6.SEC03.jar是由哪个jar间接依赖进来的,判断业务的影响范围,此处不在赘述。与上一节一样,类似的情况不一定都可以用排除jar解决。

五  通过pandora自定义插件解决依赖冲突


第四章中有讲到,如果一个应用中要同时运行两个不兼容版本的jar包,是无法通过Maven调整依赖关系解决的。第二章讲解依赖冲突原理时有提到,Pandora通过类隔离机制实现了集团各个中间件之间的隔离,Pandroa同时也支持业务方按规范创建一个可以运行在Pandora容器中的插件,容器帮业务方实现加载隔离。

联盟一淘团队就将类似IC、卡券这种核武器级存在的二方包根据自己业务的需要进行裁剪包装后,制作成Pandora插件来避免依赖冲突,取得了很好的效果。

用Pandora插件确实能在不对应用做很大调整,不影响性能的情况下完美解决依赖冲突问题。

但也有一些问题就不太适合用局部方法解决了,比如:

当维护的应用依赖过于复杂,每个应用依赖外部三四十个二方库时。这种重量级应用就会严重影响生产效率。


如上图所示,早期本人负责联盟用户平台时,就遇到两个巨无霸应用,adv(6w+代码)、pub(12w+代码)。

一方面因为依赖多,基本每周都会遇到集团各种升级,安全问题,各种小修小补,不断的上线。一方面业务发布需求也较多。

导致需要频繁发布,比如有一年个人就发布了566次。此时庞大的依赖导致部署效率,影响评估回归都会很难,此时就不应该从局部解决冲突这种视角去看,应该考虑优化应用架构,进行依赖治理,尽量避免冲突。

六  通过依赖架构治理解决依赖冲突


1  复杂依赖标准化、简化治理


首先,依赖本身就是一种复杂的业务。大部分依赖背后都有较深的业务领域知识 或者 技术领域知识。

比如我们查询搜索。

业务领域知识方面,光销量就有交易成交笔数,成交件数,搜索销量【有些订单不计入搜索销量】等。

技术领域知识方面,主搜索,联盟广告搜索引擎有时是配合使用的,比如商家未入驻广告前给商家展示货品信息就需要查主搜索,而入驻后投放下行时则需要用广告引擎。不同引擎的调用方法,结果都不一样。

如下图所示,如果我们每个业务应用都各自实现,那么各应用开发同学就要消化大量搜索客户端相关的业务、技术领域知识。成本是很高的。


面对这种情况,如果我们将这类复杂的依赖,由专人owner进行统一包装标准化【专人干专事】,会大大提升组织协同效率。如下图所示。


我们通过对主搜索,联盟引擎的统一封装。对检索条件,返回结果的标准化封装。大大降低了同学们的接入成本,以往要熟悉一个引擎的接入大概要2天,用标准化封装后的wrapper,在专人,规范文档的指导下仅0.5天就可以,大大提升效率。

2  重量级依赖代理服务化


第五节中有讲到,应用依赖的jar包过多会导致应用启动很慢,因此如果一个依赖引入jar包超过30个以上时,务必要警惕,这种依赖引入几个,就会逐步导致你工作效率大大下降。比如IC,TP,优惠中心的二方包就是典型的例子。

目前我们针对这类依赖,是直接封装一个标准代理服务,避免应用被这种巨无霸二方包拖慢。


经过以上综合治理手段,取得了很好的效果。目前联盟很少再需要大家去解决冲突问题。


参考链接:
INode讲解:http://www.cnblogs.com/itech/archive/2012/05/15/2502284.html



数据分析系统之数据管理与数据仓库
点击阅读原文查看详情!

Maven依赖版本冲突的分析及解决小结

1:前言

      做软件开发这几年遇到了许多的问题,也总结了一些问题的解决之道,之后慢慢的再遇到的都是一些重复性的问题了,当然,还有一些自己没有完全弄明白的问题。如果做的事情是重复的,遇到重复性问题的概率也就会比较多了,如果是在一个新的领域里玩,遇到的问题又都是新的,自己从来没有见过的,但是问题的解决思路基本是类似的。下面这个问题,我觉得值得一记,因为以后还会再遇到类似的,我希望自己能很快的将其解决掉。

2:报错信息

     如下是更新项目后,启动项目时抛出的部分错误信息。

十二月 14, 2016 7:52:34 下午 org.apache.catalina.core.ContainerBase addChildInternal
严重: ContainerBase.addChild: start:
org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:154)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652)
at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1779)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:618)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:565)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1487)
at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:97)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1328)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1420)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:848)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
at sun.rmi.transport.Transport$1.run(Transport.java:177)
at sun.rmi.transport.Transport$1.run(Transport.java:174)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:556)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:811)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:670)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NoClassDefFoundError: org/springframework/core/env/EnvironmentCapable
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2957)
at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1210)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1690)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2957)
at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1210)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1690)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
at java.lang.Class.getDeclaredFields0(Native Method)
at java.lang.Class.privateGetDeclaredFields(Class.java:2499)
at java.lang.Class.getDeclaredFields(Class.java:1811)
at org.apache.catalina.util.Introspection.getDeclaredFields(Introspection.java:106)
at org.apache.catalina.startup.WebAnnotationSet.loadFieldsAnnotation(WebAnnotationSet.java:270)
at org.apache.catalina.startup.WebAnnotationSet.loadApplicationListenerAnnotations(WebAnnotationSet.java:89)
at org.apache.catalina.startup.WebAnnotationSet.loadApplicationAnnotations(WebAnnotationSet.java:63)
at org.apache.catalina.startup.ContextConfig.applicationAnnotationsConfig(ContextConfig.java:415)
at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:892)
at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:386)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5416)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
... 40 more
Caused by: java.lang.ClassNotFoundException: org.springframework.core.env.EnvironmentCapable
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1720)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
... 68 more

十二月 14, 2016 7:52:34 下午 org.apache.tomcat.util.modeler.BaseModelMBean invoke
严重: Exception invoking method manageApp
java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:904)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652)
at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1779)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:618)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:565)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1487)
at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:97)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1328)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1420)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:848)

3:分析过程

     这类问题,我总结过多次,思路每次基本类似。如果是熟悉的,一看就知道哪里出问题了?如果是不熟悉的,就需要好好看看报错信息了,从自己编写的代码中找找出错的地方在哪里?如果也不熟悉,也找不到自己编写的相关代码,只能看看网上有没有其他同学遇到和解决过类的问题了。如果还是找不到问题的所在,就只能麻烦一下身边的同事帮忙看看了,多一个人多一种思路,说不定问题很快就能找到,并解决了。

     下面是我对这个问题的分析思路:

3-1:根据引起问题的提示“Caused by: java.lang.ClassNotFoundException: org.springframework.core.env.EnvironmentCapable”,看看能否找到对应的类,如下所示:

3-2:根据上图(3-1)的自我问答,看看我们的项目中依赖的jar文件是否正确,如下所示,确实是存在问题的,随后我们会发现,这个依赖的jar文件中确实没有我们使用的类。

3-3:我们确认一下自己的分析,看看对应的类是否存在于我们依赖的jar文件之中,如下图所示,确实是没有的。

3-4:通过如下图所示的方式,我们到对应的pom文件的依赖处,看看依赖的情况。

3-5:因为传递依赖导致了依赖版本的冲突,我们需要通过exclusion标签排除对应的传递依赖,试试看能否解决对应的问题

3-6:然后我们再次查看项目的依赖,确认我们的项目现在的依赖是没问题的

3-7:重新启动项目后,还是报错,我们只能再次的分析一下这个错误是怎么回事了,思路如上就不在重提了。

3-8:先看看引起错误的类是否存在,如下所示是存在的,不过却是在两个jar文件之中的,这让我首先想到可能还是传递依赖导致的依赖版本有冲突的问题

3-9:根据我们的怀疑,我们就看看我们的项目依赖jar文件是哪个吧!果然,还是低的版本!

3-10:那我们就再次的通过exclusion标签排除一下,试试吧!

3-11:成功了耶!到这里是不是有点小兴奋,毕竟我们的价值多数是源自我们解决的问题

 

4:小结

     关于Maven依赖版本冲突的小小分析就结束了,看着比较简单,不过当时自己一步步找问题的时候还是费了一些气力的,在此非常感谢同事Z的提示。现在Maven和Git这两个工具使用的越来越普及了,以后自己在实际的开发中估计会遇到越来越多的和这两个工具相关的问题。慢慢的积累吧!

以上是关于Java依赖冲突高效解决之道的主要内容,如果未能解决你的问题,请参考以下文章

开发者必看!你想知道的迁移之道都在这里了

Maven依赖版本冲突的分析及解决小结

一个高效解决冲突的Xcode利器

maven依赖冲突以及解决方法

Java 类隔离 解决依赖冲突 NoSuchMethodError

Gradle 依赖&解决依赖冲突