Jar Hell变得轻松–用jHades揭秘classpath

Java开发人员将不得不面对的最困难的问题是类路径错误: ClassNotFoundExceptionNoClassDefFoundError ,Jar Hell, Xerces Hell和company。

在本文中,我们将探究这些问题的根本原因,并了解最小的工具( JHades )如何帮助快速解决这些问题。 我们将看到为什么Maven无法(始终)防止类路径重复,并且:



  • 处理地狱的唯一方法
  • 装载机
  • 类加载器链
  • 类加载器的优先级:父优先与父末
  • 调试服务器启动问题
  • 用jHades理解Jar Hell
  • 避免类路径问题的简单策略
  • 类路径在Java 9中得到修复吗?

处理地狱的唯一方法

类路径问题的调试可能很耗时,并且往往在最坏的时间和地点发生:发布之前,通常在开发团队几乎没有访问权限的环境中。

它们也可能发生在IDE级别,并成为生产力降低的根源。 我们的开发人员往往会及早发现这些问题,这是通常的回答:

沮丧的人拉他的头发

让我们尝试为我们节省一些时间,并深入探讨这一点。 这些类型的问题很难通过反复试验来解决。 解决这些问题的唯一真正方法是真正了解正在发生的事情 ,但是从哪里开始呢?

事实证明,Jar Hell问题比其看起来要简单,并且仅需几个概念即可解决它们。 最后,造成Jar Hell问题的常见根本原因是:

  • 一个罐子不见了
  • 一个罐子太多了
  • 一个班级在什么地方不可见

但是,如果这么简单,那么为什么类路径问题很难调试?

Jar Hell堆栈跟踪不完整

原因之一是类路径问题的堆栈跟踪缺少许多信息来解决问题。 以以下堆栈跟踪为例:

java.lang.IncompatibleClassChangeError:  
Class org.jhades.SomeServiceImpl does not implement  
the requested interface org.jhades.SomeService  org.jhades.TestServlet.doGet(TestServlet.java:19)

它说一个类没有实现某个接口。 但是,如果我们查看类源代码:

public class SomeServiceImpl implements SomeService {  @Overridepublic void doSomething() {System.out.println( "Call successful!" );}
}

好了,该类显然实现了缺少的接口! 那么发生了什么呢? 问题在于堆栈跟踪缺少很多信息 ,这些信息对于理解该问题至关重要。

堆栈跟踪可能应该包含这样的错误消息(我们将了解这是什么意思):

SomeServiceImpl类加载器/路径/到/ Tomcat的/ lib中不实现接口SomeService从类加载器加载的Tomcat - Web应用程序- /路径/到/ Tomcat的/ web应用/测试

这至少是从哪里开始的指示:

  • 刚学习Java的人至少会知道,对于了解正在发生的事情,必不可少的是类加载器这一概念。
  • 很明显,涉及的一个类不是从WAR加载的,而是从服务器的某个目录( SomeServiceImpl )加载的。

什么是类加载器?

首先,类加载器只是Java类,更确切地说是运行时类的实例。 它不是 JVM不可访问的内部组件,例如垃圾收集器。

以Tomcat的WebAppClassLoader为例,这里是javadoc 。 如您所见,它只是一个普通的Java类,如果需要,我们甚至可以编写我们自己的类加载器。

ClassLoader都可以用作类加载器。 类加载器的主要职责是知道类文件的位置,然后根据JVM的要求加载类。

一切都链接到类加载器

JVM中的每个对象都通过getClass()链接到其类,而每个类都通过getClassLoader()链接到类加载器。 这意味着:

JVM中的每个对象都链接到一个类加载器!

让我们看看如何使用此事实对类路径错误方案进行故障排除。

如何查找类文件的实际位置

我们来看一个对象,看看它的类文件在文件系统中的位置:

System.out.println(service.getClass()  .getClassLoader().getResource("org/jhades/SomeServiceImpl.class"));

这是类文件的完整路径: jar:file:/Users/user1/.m2/repository/org/jhades/jar-2/1.0-SNAPSHOT/jar-2-1.0-SNAPSHOT.jar!/org/jhades/SomeServiceImpl.class
jar:file:/Users/user1/.m2/repository/org/jhades/jar-2/1.0-SNAPSHOT/jar-2-1.0-SNAPSHOT.jar!/org/jhades/SomeServiceImpl.class

如我们所见,类加载器只是一个运行时组件,它知道文件系统中查找类文件的位置以及如何加载它们。

但是,如果类加载器找不到给定的类,会发生什么?

类加载器链

缺省情况下,在JVM中,如果类加载器找不到类,则它将要求其父类加载器提供相同的类,依此类推。

这一直持续到JVM引导类加载器为止(稍后会对此进行更多介绍)。 这个类加载器链类加载器委托链

类加载器的优先级:父优先与父末

一些类加载器将请求立即委派给父类加载器,而无需先在其自己的已知目录集中搜索类文件。 据说在此模式下运行的类加载器处于“ 父优先”模式。

如果类加载器首先在本地查找类,并且仅在查询父类(如果找不到该类)之后才查找,则该类加载器被称为在“上一父代”模式下工作。

所有应用程序都有类加载器链吗?

甚至最简单的Hello World主方法也具有3个类加载器:

  • 应用程序类加载器,负责加载应用程序类(父级优先)
  • 扩展类加载器,它从$JAVA_HOME/jre/lib/ext (先是父级)加载jar
  • Bootstrap类加载器,用于加载JDK附带的任何类,例如java.lang.String (无父类加载器)

WAR应用程序的类加载器链是什么样的?

对于Tomcat或Websphere之类的应用程序服务器,类加载器链的配置与简单的Hello World主方法程序不同。 以Tomcat类加载器链为例:

Tomcat类加载器链

在这里,我们希望每个WAR都在WebAppClassLoader运行,该WebAppClassLoader以父级末尾模式工作(也可以将其设置为父级末尾)。 通用类加载器加载在服务器级别安装的库。

Servlet规范对类加载有何看法?

Servlet容器规范仅定义了类加载器链行为的一小部分:

  • WAR应用程序在其自己的应用程序类加载器上运行,可以与其他应用程序共享或不与其他应用程序共享
  • WEB-INF/classes的文件优先于其他所有文件

在那之后,任何人都可以猜测! 其余的完全开放给容器提供商解释。

为什么在供应商之间没有通用的类加载方法?

通常,默认情况下,通常将诸如Tomcat或Jetty之类的开源容器配置为先在WAR中查找类,然后才在服务器类加载器中搜索。

这使应用程序可以使用自己的库版本来覆盖服务器上可用的库。

大型铁服务器呢?

诸如Websphere之类的商业产品将尝试“出售”自己的服务器提供的库,这些库默认情况下优先于WAR上安装的库。

假设您购买了该服务器,并且还希望使用它提供的JEE库和版本,则通常会这样做。

这给部署到某些商业产品带来了极大的麻烦,因为它们的行为方式不同于开发人员用来在其工作站中运行应用程序的Tomcat或Jetty。 我们将在此解决方案上看到更多。

常见问题:重复的类版本

目前,您可能有一个很大的问题:

如果WAR中有两个罐子包含完全相同的类怎么办?

答案是行为是不确定的, 只有在运行时才会选择两个类之一 。 选择哪一个取决于类加载器的内部实现,无法预先知道。

但是幸运的是,如今大多数项目都使用Maven,Maven通过确保仅将给定jar的一个版本添加到WAR中来解决此问题。

因此,Maven项目可以不受这种特定类型的Jar Hell的影响,对吗?

为什么Maven不能防止类路径重复

不幸的是,Maven无法在所有Jar Hell情况下提供帮助。 实际上,许多不使用某些质量控制插件的Maven项目在类路径上都可以有数百个重复的类文件(我看到中继有500多个重复项)。 这有几个原因:

  • 图书馆出版商有时会更改罐子的工件名称:发生这种情况是由于品牌重塑或其他原因。 以JAXB jar为例。 Maven不可能将这些工件识别为同一罐子!
  • 某些jar具有或不具有依赖关系而发布:一些库提供程序提供jar的“具有依赖关系”版本,其中包括其他jar。 如果两个版本都具有传递依赖,则最终将导致重复。
  • 有些类在jar之间复制:有些库创建者在遇到某个类的需要时,只会从另一个项目中获取它,然后将其复制到新的jar中而不更改包名。

所有的班级文件重复都是危险的吗?

如果重复的类文件存在于同一个类加载器中,并且两个重复的类文件完全相同,那么首先选择哪个是无关紧要的–这种情况并不危险。

如果两个类文件都在同一个类加载器中,并且它们不相同,则无法在运行时选择一个,这是有问题的,并且在部署到不同环境时会表现出来。

如果类文件位于两个不同的类加载器中,则永远不会将它们视为相同(请参见后面的类标识危机部分)。

如何避免WAR类路径重复?

例如,可以通过使用Maven Enforcer插件来避免此问题,并启用“ 禁止重复类”的额外规则。

您也可以使用JHades WAR重复类报告快速检查您的WAR是否干净。 该工具可以过滤“无害”重复项(相同的类文件大小)。

但是,即使是干净的WAR也会存在部署问题:类丢失,从服务器而不是WAR中获取的类以及版本错误的类,类强制转换异常等。

使用JHades调试类路径

类路径问题通常在应用服务器启动时出现,这是一个特别糟糕的时刻,尤其是在部署到访问受限的环境中时。

JHades是帮助处理Jar Hell的工具(免责声明:我写的)。 它是一个单一的Jar,除了JDK7本身之外,没有任何依赖性。 这是一个如何使用它的示例:

new JHades().printClassLoaders().printClasspath().overlappingJarsReport().multipleClassVersionsReport().findClassByName("org.jhades.SomeServiceImpl")

这会将类加载器链,罐子,重复类等打印到屏幕上。

调试服务器启动问题

在服务器无法正常启动的情况下,JHades可以很好地工作。 提供了一个servlet侦听器,即使在应用程序的任何其他组件开始运行之前,该侦听器也可以打印类路径调试信息。

ClassCastException和类身份危机

对Jar Hell进行故障排除时,请注意ClassCastExceptions 。 在JVM中,不仅通过完全限定的类名来标识类,而且通过其类加载器来标识该类。

这是违反直觉的,但事后看来是有道理的:我们可以使用相同的包和名称创建两个不同的类,将它们放入两个jar中,然后放入两个不同的类加载器中。 可以说一个扩展了ArrayList ,另一个是Map

因此,这些类是完全不同的(尽管名称相同),并且不能相互转换! 运行时将抛出CCE以防止发生这种潜在的错误情况,因为无法保证这些类是可强制转换的。

将类加载器添加到类标识符是Java早期发生的类身份危机的结果。

避免类路径问题的策略

说起来容易做起来难,但是避免与类路径相关的部署问题的最佳方法是在“上一步”模式下运行生产服务器。

这样,WAR的类版本优先于服务器上的类版本,并且在生产环境和开发人员工作站中使用了相同的类,这些工作站可能正在使用Tomcat,Jetty或其他开源的Parent Last服务器。

在某些服务器(例如Websphere)中,这还不够,并且您还必须在清单文件中提供特殊属性以显式关闭某些库,例如JAX-WS。

修复Java 9中的类路径

在Java 9中,类路径已完全通过新的Jigsaw模块化系统进行了改进。 在Java 9中,可以将jar声明为模块,它将在其自己的隔离类加载器中运行,该类加载器以OSGI方式从其他类似的模块类加载器读取类文件。

如果需要,这将允许同一版本的Jar的多个版本共存。

结论

最后,Jar Hell问题并不是像最初看起来那样低级或难以解决。 都是关于zip文件(jar)在某些目录中存在/不存在,如何查找这些目录以及如何在访问受限的环境中调试类路径。

通过了解一组有限的概念(例如类加载器,类加载器链和父级/父级后代模式),可以有效地解决这些问题。

外部链接

这份演讲“您真的从ZeroTurnaround的Jevgeni Kabanov( JRebel公司) 获得类加载器”是有关Jar Hell以及与类路径相关的不同类型异常的重要资源。

翻译自: https://www.javacodegeeks.com/2014/10/jar-hell-made-easy-demystifying-the-classpath-with-jhades.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/365609.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图像处理中常见的时域与频域区别与关系

本文纯为转载只做个人学习记录用,请自动点击链接到作者原文:https://blog.csdn.net/samkieth/article/details/49561539 一、什么是时域 时域是描述数学函数或物理信号对时间的关系。例如一个信号的时域波形可以表达信号随着时间的变化。 二、什么是频域…

程序控制发送文件到邮箱_Kindle电子邮箱推送

Kindle 推送支持的格式目前 Kindle 推送仅支持以下几种格式。需要注意的是,AZW 和 AZW3 是两种不同的格式,虽然这两种格式 Kindle 设备都支持阅读,但是亚马逊的个人文档服务支持推送 AZW 但是不支持 AZW3。Kindle 格式 (.mobi 或 .azw) * 推荐…

[USACO06JAN] 牛的舞会 The Cow Prom

题目描述 The N (2 < N < 10,000) cows are so excited: its prom night! They are dressed in their finest gowns, complete with corsages and new shoes. They know that tonight they will each try to perform the Round Dance. Only cows can perform the Round D…

前端js 实现文件下载

https://www.zhangxinxu.com/wordpress/2017/07/js-text-string-download-as-html-json-file/ 侵删 1.H5 download属性 function downFile(content, filename) {// 创建隐藏的可下载链接var eleLink document.createElement(a);eleLink.download filename;eleLink.style.disp…

mysql的英文字母_MySQL中查询的有关英文字母大小写问题的分析

mysql数据库在做查询时候&#xff0c;有时候是英文字母大小写敏感的&#xff0c;有时候又不是的&#xff0c;主要是由mysql的字符校验规则的设置决定的&#xff0c;通常默认是不支持的大小写字母敏感的。1. 什么是字符集和校验规则&#xff1f;字符集是一套符号和编码。校对规则…

JDK8 lambda的会话指南–术语表

上次出现…我写了一篇与JDK8为我们提供的新方法有关的文章。 最令我兴奋的功能是lambda。 我必须承认&#xff0c;在即将成为浪子的第一年&#xff08;在此期间&#xff0c;我使用C&#xff03;开发了该产品&#xff09;&#xff0c;我喜欢LINQ和它可以做的漂亮&#xff0c;优雅…

写接口给别人调用 推送数据到我们_我们写了一个超好用的抖音矩阵数据管理工具...

我最近跑了十来个抖音号&#xff0c;遇到一些问题&#xff0c;然后通过我们NB的程序员解决了。如果你也在做抖音矩阵&#xff0c;那这些问题你肯定也会遇到&#xff0c;所以我把解决问题的方法工具化了&#xff0c;给大家用。我遇到的最大的问题&#xff0c;就是账号数据的同步…

php crypt mysql password_使用PHP 5.5的password_hash和password_verify函数

使用PHP 5.5的password_hash和password_verify函数假设我想为用户存储密码&#xff0c;这是使用PHP 5.5的password_hash()功能(或者这个版本的PHP 5.3.7&#xff1a;https&#xff1a;//github.com/ircmaxell/password_compat)的正确方法吗&#xff1f;$options array("c…

Mysql order by 导致 using filesorting

https://www.cnblogs.com/drcoding/p/4942277.html转载于:https://www.cnblogs.com/eason-d/p/9700526.html

angular 拼接html 事件无效

主要是要引用$compile方法 更多专业前端知识&#xff0c;请上 【猿2048】www.mk2048.com

更好地利用Pmd,Findbugs和CheckStyle的结果。

我们可以列举许多Java静态分析工具&#xff0c;每种工具都专注于特定领域并具有其优势&#xff0c;我们可以列举一下&#xff1a; Pmd是基于静态规则集的Java源代码分析器&#xff0c;它识别潜在的问题&#xff0c;例如&#xff1a; 可能的错误-空的try / catch / finally / s…

Java基础知识(数据类型和集合)

一、数据类型 包装类型 包装类型是对基本数据类型不足之处的补充。 基本数据类型的传递方式是值传递&#xff0c;而包装类型是引用传递&#xff0c;同时提供了很多数据类型间转换的方法。 Java1.5 以后可以自动装箱和拆箱 二、集合 List&#xff1a;有序、可重复。可以通过索引…

python 局域网 主机名_使用python获取连接到本地网络(基于主机名)的所有设备的ip...

这绝对不是重复的。在我正在做一个应用程序&#xff0c;我需要找到我的设备的IP地址。我知道他们的名字&#xff0c;通过他们的名字我需要得到他们的知识产权。Linux应该很简单&#xff0c;但我需要跨平台的态度&#xff0c;因此我使用python。在我已经知道解决方案&#xff1a…

mfc使用cef源代码实现_如何获得微信小游戏跳一跳源码以及源代码组合包括哪些...

很多小游戏都是由源代码编写而成的&#xff0c;那大家知道源代码组合包括哪些吗?手机游戏源代码怎么使用的呢?还有&#xff0c;如何获得微信小游戏跳一跳源码?下面就由奇瑰网小编带大家来了解一下相关的内容吧。   源代码组合包括哪些   源代码作为软件的特殊部分&#…

js 分页插件(jQuery)

参考&#xff1a;http://www.jb51.net/article/117191.htm 侵删 css 部分 charset "utf8"; *{box-sizing: border-box;padding: 0;margin: 0; } .page{font-size: 13px;text-align: center;margin-top: 20px; } .page .page_to{display: inline-block;max-width: 25…

机器学习过程中欠拟合和过拟合的诊断及解决方法

1.Diagnosing bias vs. variance 2.Regularization and bias/variance 3.Learning curves 4.Deciding what to try next 转载于:https://www.cnblogs.com/CoolJayson/p/9704385.html

微信 python 2020_2020年最新的Python操控微信教程

​自从微信禁止网页版登陆之后&#xff0c;itchat 库实现的功能也就都不能用了&#xff0c;那现在 Python 还能操作微信吗&#xff1f;答案是还可以。目前有一个项目 WechatPCAPI 可以对微信进行操作&#xff0c;简单来说它是直接操作 PC 版微信客户端的&#xff0c;当然它有一…

高级Java泛型:检索泛型类型参数

在JDK5中引入Java泛型之后&#xff0c; Java泛型Swift成为许多Java程序的组成部分。 但是&#xff0c;乍一看似乎很简单的Java泛型&#xff0c;程序员很快就会迷失此功能。 大多数Java程序员都知道Java编译器的类型擦除 。 一般来说&#xff0c;类型擦除意味着有关Java类的所有…

php 算法

<? //-------------------- // 基本数据结构算法 //-------------------- //二分查找&#xff08;数组里查找某个元素&#xff09; function bin_sch($array, $low, $high, $k){ if ( $low < $high){ $mid intval(($low$high)/2 ); if…

python利用opencv去除图片logo_利用python和opencv批量去掉图片黑边

import osimport cv2import numpy as npfrom scipy.stats import modeimport timeimport concurrent.futures‘‘‘multi-process to crop pictures.‘‘‘def crop(file_path_list):origin_path, save_path file_path_listimg cv2.imread(origin_path)gray cv2.cvtColor(im…