`

关于JAR包版本冲突的几个应对招数总结

 
阅读更多
概述

   Javeer们一定遇到过NoSuchMethodError的错误,一旦碰到这种错误,必是JAR包版本冲突的问题无疑,版本冲突分开为以下两种情况:
  • 同构件多版本冲突:类路径同时中存在多个相同构件的版本,如即存在poi-ooxml-3.11.jar,又存在poi-ooxml-3.9.jar,项目真正运行时需要的是3.11,而JVM加载到的是3.9,这种情况,我们称之为“同构多版本冲突”;
  • 协作构件版本冲突:假设构件A依赖于构件B,A的x版本需要B的y版本,如果引入的是B的z版本,那么A和B就不能很好的搭档,冲突产生,此为“协作构件版本冲突”。

    构件版本冲突既可以在开发环境时发生,也有可能在开发环境下没有问题,部署到生产环境下才发生。这是因为JVM的类加载机制决定的,当JVM需要某个Class时,它先看JVM内部有没有这个Class,如果有就直接使用,如果没有才在类路径下加载新的JAR。如果JVM加载到A-x.jar,但实现上我们却需要A-y.jar,则恶魔就从瓶子里出来了。

解决招数

  同构件多版本冲突
  对于我们需要A-x.jar,JVM却加载到A-y.jar的情况,我们只要将类路径下那个A-y.jar移除,或者通过应用服务器的JAR优先加载顺序机制让A-x.jar优先于A-y.jar就OK了。对于TOMCAT,WAS等应用服务器,都有设置JAR的优先加载策略,如是以项目的类路径优先,还是应用服务器的共享类路径优先。如果是以共享类路径优先,假设共享类路径下有一个A-y.jar,则你项目WEB-INF/lib下的A-x.jar就加载不到。
   所以解决这个问题的核心在于:找到运行期Class类到底是从哪个JAR包加载的。在开发环境下,可以使用断点查看类实例源自的JAR,下面提供了一个获取实例类所在位置的工具类:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
public class ClassLocationUtils {

    public static String where(String  className){
        try {
            Class<?> theClazz = Class.forName(className);
            return where(theClazz);
        } catch (ClassNotFoundException e) {
            return "CLASS_NOT_FOUND:"+className;
        }
    }

    /**
     * 获取类所有的路径
     * @param cls
     * @return
     */
    public static String where(final Class cls) {
        if (cls == null)throw new IllegalArgumentException("null input: cls");
        URL result = null;
        final String clsAsResource = cls.getName().replace('.', '/').concat(".class");
        final ProtectionDomain pd = cls.getProtectionDomain();
        if (pd != null) {
            final CodeSource cs = pd.getCodeSource();
            if (cs != null) result = cs.getLocation();
            if (result != null) {
                if ("file".equals(result.getProtocol())) {
                    try {
                        if (result.toExternalForm().endsWith(".jar") ||
                                result.toExternalForm().endsWith(".zip"))
                            result = new URL("jar:".concat(result.toExternalForm())
                                    .concat("!/").concat(clsAsResource));
                        else if (new File(result.getFile()).isDirectory())
                            result = new URL(result, clsAsResource);
                    }
                    catch (MalformedURLException ignore) {}
                }
            }
        }
        if (result == null) {
            final ClassLoader clsLoader = cls.getClassLoader();
            result = clsLoader != null ?
                    clsLoader.getResource(clsAsResource) :
                    ClassLoader.getSystemResource(clsAsResource);
        }
        return result.toString();
    }
}

    在任何断点处动态执行该类就可看到类源自的JAR包了,找到JAR包你就可以分析到底是不是你想要的那个版本(下图是演示在IDE下通过ALT+F8打开Evaluate Expression对话框动态查看Class所在JAR包):



    在生产环境下,你可以通过写LOG日志输出来查看,但这种方式需要你代码中有写日志的代码,可能涉及到重新编译和部署,肯定不是最好的方式。最好的是打开一个页面,通过输入类名获取类所在的路径信息,下面是一个完成此功能的JSP:
<%@page contentType="text/html; charset=UTF-8"%>
<%@page import="java.io.File,java.net.*,java.io.*"%>
<%!

  public static URL getClassLocation(final Class cls) {
    if (cls == null)throw new IllegalArgumentException("null input: cls");
    URL result = null;
    final String clsAsResource = cls.getName().replace('.', '/').concat(".class");
    final ProtectionDomain pd = cls.getProtectionDomain();
    // java.lang.Class contract does not specify if 'pd' can ever be null;
    // it is not the case for Sun's implementations, but guard against null
    // just in case:
    if (pd != null) {
      final CodeSource cs = pd.getCodeSource();
      // 'cs' can be null depending on the classloader behavior:
      if (cs != null) result = cs.getLocation();
      if (result != null) {
        // Convert a code source location into a full class file location
        // for some common cases:
        if ("file".equals(result.getProtocol())) {
          try {
            if (result.toExternalForm().endsWith(".jar") ||
                result.toExternalForm().endsWith(".zip"))
              result = new URL("jar:".concat(result.toExternalForm())
                               .concat("!/").concat(clsAsResource));
            else if (new File(result.getFile()).isDirectory())
              result = new URL(result, clsAsResource);
          }
          catch (MalformedURLException ignore) {}
        }
      }
    }
    if (result == null) {
      // Try to find 'cls' definition as a resource; this is not
      // document.d to be legal, but Sun's implementations seem to         //allow this:
      final ClassLoader clsLoader = cls.getClassLoader();
      result = clsLoader != null ?
          clsLoader.getResource(clsAsResource) :
          ClassLoader.getSystemResource(clsAsResource);
    }
    return result;
  }
%>
<html>
<head>
<title>srcAdd.jar</title>
</head>
<body bgcolor="#ffffff">
  使用方法,className参数为类的全名,不需要.class后缀,如
  srcAdd.jsp?className=java.net.URL
<%
try
{
  String classLocation = null;
  String error = null;
  String className = request.getParameter("className");

  classLocation =  ""+getClassLocation(Class.forName(className));
  if (error == null) {
    out.print("类" + className + "实例的物理文件位于:");
    out.print("<hr>");
    out.print(classLocation);
  }
  else {
    out.print("类" + className + "没有对应的物理文件。<br>");
    out.print("错误:" + error);
  }
}catch(Exception e)
{
  out.print("异常。"+e.getMessage());
}
%>
</body>
</html>

   打开页面,通过URL参数指定类名,页面即可显示类加载自哪个JAR了,非常方便!

  协作构件版本冲突
针对协作构件之间的版本冲突,其实就是要找到协作构件之间的匹配版本了,一般情况下,主构件的发布网站都会有相关帮助文档给出明确的说明,如Apache POI,其poi-ooxml构件需要依赖于poi-ooxml-schemas构件,版本的匹配关系说明如下:
引用

poi-ooxml requires poi-ooxml-schemas. This is a substantially smaller version of the ooxml-schemas jar (ooxml-schemas-1.3.jar for POI 3.14 or later, ooxml-schemas-1.1.jar for POI 3.7 up to POI 3.13, ooxml-schemas-1.0.jar for POI 3.5 and 3.6). The larger ooxml-schemas jar is normally only required for development. Similarly, the ooxml-security jar, which contains all of the classes relating to encryption and signing, is normally only required for development. A subset of its contents are in poi-ooxml-schemas. This JAR is ooxml-security-1.1.jar for POI 3.14 onwards and ooxml-security-1.0.jar prior to that.

   以上说明引用自apache的网站:http://poi.apache.org/overview.html
  
     再如Mockito和PowerMock版本匹配也可以从powermock的网站中找到:
https://github.com/jayway/powermock/wiki/MockitoUsage 

   
     简单的方法当然是找搜索引擎了,度娘找这种不够了,谷哥不在,用bing吧,关键词把两个构件名写入搜索,如在bing中搜索:


小结
    相对来说,同构件多版本冲突好解决些,找到源JAR看一眼不对换掉或更改应用服务器JVM 类加载顺序即可。但构件版本匹配冲突则不太好解决,一定要尽量上其官网查资料,不要做无谓的换包尝试哦。

以后在此维护新文章:
https://www.jianshu.com/u/d7f090245ddd

  • 大小: 43.1 KB
  • 大小: 119.3 KB
  • 大小: 42.9 KB
分享到:
评论
1 楼 飞天奔月 2016-08-03  
程序员必须要会 fangqian,技术问题 还是需要 google的

相关推荐

    easyExcel、poi,jar包版本冲突,jar包处理

    不会与poi的版本产生冲突 4、加载至maven中 5、涉及到的jar冲突,取决于自己的项目 pom示例:项目中存在冲突再 exclusion 添加即可 &lt;groupId&gt;com.customize&lt;/groupId&gt; &lt;artifactId&gt;easyexcel &lt;version&gt;2.2.6 ...

    osgi解决不同版本jar包冲突的demo

    OSGi(Open Services Gateway Initiative)是一种模块化系统和Java服务框架,它允许在单个JVM上运行多个版本的相同库或服务,从而解决了不同版本jar包冲突的问题。本示例通过一个名为“myself”的工程,展示了如何...

    jar包冲突检测工具.zip

    jar包冲突通常发生在引入了多个依赖库,而这些库中有相同类的不同版本时。"jar包冲突检测工具"正是为了解决这个问题而设计的。 这个工具的主要功能是帮助开发者检测出项目中可能存在的jar包冲突。它的工作原理可能...

    jar包冲突检测工具

    在Java开发过程中,jar包冲突是一个常见的问题,尤其是在大型项目或者依赖复杂的系统中。jar包冲突指的是多个不同的jar包中包含相同类的情况,这可能导致运行时错误或不可预测的行为。"jar包冲突检测工具"正是为了...

    检查jar包冲突

    以下是一些关于如何检查和解决`jar`包冲突的关键知识点: 1. **冲突检测**: - **MANIFEST.MF检查**:每个`jar`包的`META-INF/MANIFEST.MF`文件可以列出其依赖项,通过比较不同`jar`包的`MANIFEST.MF`,可以初步...

    检测工具 jar包冲突

    在Java开发过程中,"jar包冲突"是一个常见的问题,它主要出现在项目中引用了多个库,而这些库可能包含了相同类的不同版本。这种情况可能导致运行时错误或者程序行为不一致,因为JVM在加载类时可能会遇到混淆。"检测...

    解决网上应用厅部署在was上jar包冲突问题

    通过调整类加载顺序为“类已载入并且先使用本地类装入器(父类最后)”,可以确保应用优先使用自身WAR包中的JAR包,从而避免版本冲突问题。 #### 扩展阅读 - **类加载机制**:深入了解Java的类加载机制对于理解和...

    weblogic部署项目jar冲突解决

    在将一个已经在Tomcat上正常运行的应用迁移到WebLogic时,可能会遇到各种问题,其中最常见的是配置问题和JAR包冲突。 标题"weblogic部署项目jar冲突解决"指出的核心问题是JAR包冲突。这是因为不同的应用服务器可能...

    关于Myeclipse使用mybatis出现Jar包冲突的解决办法

    例如,MyEclipse自带的Mybatis版本可能较旧,而我们的项目可能需要使用更新的版本,这就可能导致类路径中有两个不同版本的相同Jar包,从而产生冲突。 解决此类问题的步骤如下: 1. **检查冲突**:首先,我们需要...

    easyExcel2.2.6、poi3.17独立版jar包,解决使用easyExcel2.2.6时poi3.17与3.9或4.1.2jar包冲突

    easyExcel支持最低的POI版本是3.17,现有项目若已用POI版本比这高的话,希望不改变现有的引用,解决jar包冲突的话,使用该独立版的jar包。该包已改过包名,亲测可兼容使用,网上有说把poi升级到4.1.2但是试了导出...

    SSH整合jar包,彻底解决集成jar包冲突问题

    6. **使用jar包管理工具**:例如,使用FatJar或者One-Jar等工具,将所有依赖打包到一个可执行的jar中,避免运行时环境中的jar包冲突。 提供的"SSH整合JAR包"压缩文件,可能是为了方便开发者快速搭建SSH环境,包含了...

    jar包修改神器.rar

    这个压缩包文件提供了工具和资源,帮助开发者修改jar包内的package包名,从而避免不同版本jar包之间的冲突。 核心工具是`jarjar-1.4.jar`,这是一个开源工具,专门用于重命名或移动Java类,以便在一个项目中整合多...

    将多个jar包合并为一个jar包

    为了简化项目结构、减少资源占用以及方便管理,开发者有时会选择将多个JAR包合并成一个单一的JAR包。这个过程被称为JAR文件的合并。本文将详细讲解如何进行这项操作,并分享一个实用的合并工具。 首先,理解JAR文件...

    maven冲突jar包清理脚本

    setting处理了,也将jar包的版本号都试了一遍,还是没解决,后来请教大牛,原来是没连网,maven工程加载了一部分jar包,连网之后自动下载的jar包跟之前的冲突,用这个脚本放在Maven仓库的目录,直接打开,可以将之前...

    myeclipse2014配置好的注解包,Jar包没有冲突

    本资源"myeclipse2014配置好的注解包,Jar包没有冲突"旨在解决一个常见的开发问题:库文件(Jar包)的版本冲突。在Java开发中,尤其是使用MyEclipse 2014时,由于不同库可能依赖不同版本的其他库,导致版本冲突是很...

    asm.jar包冲突

    标题中的“asm.jar包冲突”指的是在开发或运行Java应用程序时遇到的问题,ASM是一个Java字节码操控和分析框架,常用于动态代理、字节码增强等场景。当多个库或者项目引用了不同版本的asm.jar,就可能出现版本冲突,...

    jar包 jar包jar包 jar包jar包jar包jar包jar包jar包

    jar包 jar包jar包 jar包jar包jar包jar包jar包jar包jar包 jar包jar包 jar包jar包jar包jar包jar包jar包jar包 jar包jar包 jar包jar包jar包jar包jar包jar包

    解决jar包冲突的工具类jarjar-1.4.rar

    解决jar包冲突的工具类jarjar-1.4.rar,jar包冲突解决方法,解决jar与jar冲突,jar与aar冲突,不想删除任何一个包,只能修改其中一个jar包包名即可解决,解决方法详细请看这篇文章: ...

    如何在was中解决jar包冲突.pdf

    jar 包冲突问题是在大型应用项目的开发中经常遇到的问题,它是由于不同的应用程序使用相同的共享 jar 包,但是这些 jar 包的版本不同所造成的。为了解决这个问题,我们需要了解 WAS 中类加载器的工作机制。 WAS 中...

    jarjar-1.4.jar

    在Java开发过程中,我们经常会遇到一个头疼的问题:jar包冲突。这是因为不同的库或者框架可能会包含相同类的不同版本,导致运行时出现错误。为了解决这个问题,开发者们开发了一款名为"jarjar"的工具,其核心功能是...

Global site tag (gtag.js) - Google Analytics