Jar Hell变得轻松–用jHades揭开类路径的神秘面纱

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

在本文中,我们将探究这些问题的根本原因,并了解最小的工具( 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个类加载器:

  • 应用程序类加载器,负责加载应用程序类(父级优先)
  • Extensions类加载器,它从$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/339711.shtml

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

相关文章

韩顺平php教程笔记,PHP笔记,韩顺平php笔记_PHP教程

PHP笔记,韩顺平php笔记Mysql-----------------------------------------------------------------------------Mysql 改默认密码update mysql.user set PasswordPASSWORD(123456) WHERE UserrootFLUSH PRIVILEGES去了解下MYSQL的范式、反范式、混合范式还有索引的建…

php键盘输入函数,php的常用输入语句以及常用函数

这篇文章主要介绍了关于php的常用输入语句以及常用函数&#xff0c;有着一定的参考价值&#xff0c;现在分享给大家&#xff0c;有需要的朋友可以参考一下一、 echo语句echo做php的人在熟悉不过了&#xff0c;在php文件中我们用他来输出数据。<?php echo "hi mm"…

分度器中硒定位器的完整指南(示例)

在测试网站的功能时&#xff0c;特别是Web元素&#xff08;例如单选按钮&#xff0c;文本框&#xff0c;下拉列表等&#xff09;&#xff0c;您需要确保能够访问这些元素。 Selenium定位器正是出于这个目的&#xff0c;通过使用此命令&#xff0c;我们可以识别这些Web元素DOM&a…

ajax php 投票,PHP 实例 AJAX 投票

PHP 实例 - AJAX 投票AJAX 投票在下面的实例中&#xff0c;我们将演示一个投票程序&#xff0c;通过它&#xff0c;投票结果在网页不进行刷新的情况下被显示。你喜欢 PHP 和 AJAX 吗?是:否:实例解释 - HTML 页面当用户选择上面的某个选项时&#xff0c;会执行名为 "getVo…

wildfly管理控制台_WildFly 9 –别希望您的控制台像这样!

wildfly管理控制台每个人都可能听到这个消息。 周一发布了第一个WildFly 9.0.0.Alpha1版本。 您可以从wildfly.org网站上下载它&#xff0c;最大的变化是它是由一个新的功能配置工具构建的&#xff0c;该工具位于现在单独的核心发行版上&#xff0c;还包含一个新的Servlet发行版…

azure mysql sql,UiPath连接Azure Sql Server数据库

一、创建数据库在Azure中创建SQL数据库image更改防火墙设置&#xff0c;并设置客户端IP访问规则image二、安装数据源驱动在本地安装数据源驱动程序&#xff0c;保证可以正常接入到远程的数据库。如果不安装驱动程序&#xff0c;则会出现以下报错&#xff1a;[Microsoft][ODBC D…

Spring Boot删除嵌入式Tomcat服务器,启用Jetty服务器

快速指南&#xff0c;在Spring Boot应用程序中排除嵌入式tomcat服务器并添加Jetty Server。 配置删除tomcat并添加Jetty Server。 1.简介 在本教程中&#xff0c;我们将学习如何从Spring Boot应用程序中删除Tomcat服务器 。 实际上&#xff0c;一旦我们添加了“ spring-boot-s…

java 方式配置ssm,关于SSM以及Spring boot中对于Spring MVC配置的问题

SSM中 Spring MVC配置传统的web.xml配置web.xmlcontextConfigLocationclasspath*:applicationContext.xmlorg.springframework.web.context.ContextLoaderListenerencodingFilterorg.springframework.web.filter.CharacterEncodingFilterencodingUTF-8encodingFilter/*SpringMV…

java 并发的原子性_Java并发教程–原子性和竞争条件

java 并发的原子性原子性是多线程程序中的关键概念之一。 我们说一组动作是原子的&#xff0c;如果它们都以不可分割的方式作为单个操作执行。 认为多线程程序中的一组操作将被串行执行是理所当然的&#xff0c;可能会导致错误的结果。 原因是由于线程干扰&#xff0c;这意味着…

linux 误删除mysql表能恢复吗,Linux误删数据恢复

引子指在键上飘&#xff0c;难免会湿手套。当你按下shiftdel键后&#xff0c;会不会突然心里凉透&#xff0c;当你执行rm -rf后&#xff0c;会不会马上去搜索哪个国家入境不需要签证。或者你还会遇到如下的情况&#xff1a;root4xem7:~# aliasalias cdrm -rfalias ddocker数据恢…

Apache Camel 3.1 –更多骆驼核心优化(第3部分)

我以前曾在博客中介绍过我们在下一个Camel 3.1版本中所做的优化 博客第1部分 博客第2部分 今天&#xff0c;我想简短介绍一下我们已经完成的最新开发&#xff0c;因为我们准备在本周末或下半年准备好构建和发布Camel 3.1。 从第2部分开始&#xff0c;我们设法在路由过程中将…

mysql error 1114,mysql error 1114 table is full 处理分享

问题描述&#xff1a;一、早上上班收到报警&#xff0c;用户中心某slave不同步。二、查看情况&#xff0c;发现mysql error 1114&#xff0c;The table ‘xxxx’ is full 。。。。。三、检查其他slave&#xff0c;都出现同样问题。四、解决问题方案1、网上解决方案a、修改tmp_…

jvm jinfo 参数_jinfo:JVM运行时配置的命令行浏览

jvm jinfo 参数在最近的一些博客中&#xff08;特别是在对Java EE 7性能调优和优化以及WildFly性能调优的书中的评论中&#xff09;&#xff0c;我引用了自己过去在某些Oracle JDK命令行工具上的博客文章。 令我震惊的是&#xff0c;我从来没有专门解决过漂亮的jinfo工具&#…

matlab int 积不出,matlab – 点积:*命令与循环给出不同的结果

我在Matlab,z和beta中有两个向量.矢量z是117&#xff1a;1 0.430742139435890 0.257372971229541 0.0965909090909091 0.694329541928697 0 0.394960106863064 0 0.100000000000000 1 0.264704325268675 0.387774594078319 0.269207605609567 0.472226643323253 0.750000000000…

49自动化测试中最常见的硒异常

开发人员将始终在编写代码时牢记不同的场景&#xff0c;但是在某些情况下&#xff0c;实现可能无法按预期工作。 相同的原则也适用于测试代码&#xff0c;该代码主要用于测试现有产品的功能&#xff0c;发现错误以及使产品100&#xff05;不受错误影响。 正确地说&#xff0c;…

鹰式价差matlab,鹰式期权:什么叫铁鹰式期权组合,蝶式价差期权?

蝶式期权套利 是利用 交割月份的价差进行 套期获利&#xff0c; 个方向相 反、 共享居中交割月份合约的跨期套利组成。是一种期权策略&#xff0c;风险有限&#xff0c;盈利也有限&#xff0c;是由一手牛市套利和一手熊市套利组合而成的。铁鹰式期权组合是牛市看跌价差期权组合…

angular8 rest_带有Angular JS的Java EE 7 – CRUD,REST,验证–第2部分

angular8 rest这是Angular JS承诺的Java EE 7的后续版本–第1部分 。 花了比我预期更长的时间&#xff08;找到时间来准备代码和博客文章&#xff09;&#xff0c;但是终于到了&#xff01; 应用程序 第1部分中的原始应用程序只是带有分页的简单列表&#xff0c;以及提供列表数…

php如果字符串有1 3 5,Day3-php 字符串1

字符串是由一系列字符组成&#xff0c;在PHP中&#xff0c;字符和字节一样&#xff0c;也就是说&#xff0c;一共有256种不同字符的可能性。1、字符串 定义方法字符串型可以用三种方法定义&#xff1a;单引号形式、双引号形式和Heredoc结构形式。单引号&#xff1a;不会解析变量…

带有Java Pojo作为输入输出示例的AWS Lambda函数

在上一教程中&#xff0c;我们看到了如何使用Java创建AWS Lambda函数&#xff0c;并传递了String作为输入&#xff0c;还返回了String作为Output。如果您是第一次创建lambda函数&#xff0c;我建议先阅读该教程。 在本教程中&#xff0c;我们将看到如何传递Java普通的旧Java对…

bing搜索php,PHP 使用bing搜索网站的api封装类用法

/*** 使用bing api搜索网站的PHP封装类** param* author 编程之家 jb51.cc jb51.cc**/class BingAPI{var $accountKey ;var $ServiceRootURL https://api.datamarket.azure.com/Bing/Search/;var $WebSearchURL;var $searchText;var $searchType;var $request_data;var $Auto…