【Maven】Maven 基础教程(五): jar 包冲突问题

本文介绍了Maven基础教程中的第五部分,详细阐述了jar包冲突在开发中的常见表现形式,如找不到类和找不到方法,以及如何通过IDEA的MavenHelper插件和Maven的enforcer插件进行冲突检测和解决。强调了统一管理和版本仲裁的重要性。
摘要由CSDN通过智能技术生成

Maven 基础教程》系列,包含以下 5 篇文章:

😊 如果您觉得这篇文章有用 ✔️ 的话,请给博主一个一键三连 🚀🚀🚀 吧 (点赞 🧡、关注 💛、收藏 💚)!!!您的支持 💖💖💖 将激励 🔥 博主输出更多优质内容!!!

8.jar 包冲突问题

编订依赖列表的程序员,初次设定一组依赖,因为尚未经过验证,所以确实有可能存在各种问题,需要做有针对性的调整。那么谁来做这件事呢?我们最不希望看到的就是:团队中每个程序员都需要自己去找依赖,即使是做同一个项目,每个模块也各加各的依赖,没有统一管理。那前人踩过的坑,后人还要再踩一遍。而且大家用的依赖有很多细节都不一样,版本更是五花八门,这就让事情变得更加复杂。

所以虽然初期需要根据项目开发和实际运行情况对依赖配置不断调整,最终确定一个各方面都 OK 的版本。但是一旦确定下来,放在父工程中做依赖管理,各个子模块各取所需,这样基本上就能很好的避免问题的扩散。

即使开发中遇到了新问题,也可以回到源头检查、调整 dependencyManagement 配置的列表,而不是每个模块都要改。

8.1 表现形式

由于实际开发时我们往往都会整合使用很多大型框架,所以一个项目中哪怕只是一个模块也会涉及到大量 jar 包。数以百计的 jar 包要彼此协调、精密配合才能保证程序正常运行。而规模如此庞大的 jar 包组合在一起难免会有磕磕碰碰。最关键的是由于 jar 包冲突所导致的问题非常诡异,这里我们只能罗列较为典型的问题,而没法保证穷举。

但是我们仍然能够指出一点:一般来说,由于我们自己编写代码、配置文件写错所导致的问题 通常能够在异常信息中看到我们自己类的全类名或配置文件的所在路径。如果整个错误信息中完全没有我们负责的部分,全部是框架、第三方工具包里面的类报错,这往往就是 jar 包的问题所引起的。

而具体的表现形式中,主要体现为 找不到类找不到方法

8.1.1 抛异常:找不到类

此时抛出的常见的异常类型:

  • java.lang.ClassNotFoundException:编译过程中找不到类。
  • java.lang.NoClassDefFoundError:运行过程中找不到类。
  • java.lang.LinkageError:不同类加载器分别加载的多个类有相同的全限定名。

我们来举个例子:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.x.x</version>
</dependency>

httpclient 这个 jar 包中有一个类:org.apache.http.conn.ssl.NoopHostnameVerifier。这个类在较低版本中没有,但在较高版本存在。比如:

jar 包版本是否存在
4.3.6
4.4

那当我们确实需要用到 NoopHostnameVerifier 这个类,我们看到 Maven 通过依赖传递机制引入了这个 jar 包,所以没有明确地显式声明对这个 jar 包的依赖。可是 Maven 传递过来的 jar 包是 4.3.6 版本,里面没有包含我们需要的类,就会抛出异常。

冲突 体现在:4.3.64.4 这两个版本的 jar 包都被框架所依赖的 jar 包给传递进来了,但是假设 Maven 根据 版本仲裁 规则实际采纳的是 4.3.6

版本仲裁:Maven 的版本仲裁机制只是在没有人为干预的情况下,自主决定 jar 包版本的一个办法。而实际上我们要使用具体的哪一个版本,还要取决于项目中的实际情况。所以在项目正常运行的情况下,jar 包版本可以由 Maven 仲裁,不必我们操心;而发生冲突时 Maven 仲裁决定的版本无法满足要求,此时就应该由程序员明确指定 jar 包版本。

  • 最短路径优先:在下图的例子中,对模块 pro25-module-a 来说,Maven 会采纳 1.2.12 版本。

在这里插入图片描述

  • 路径相同时先声明者优先:此时 Maven 采纳哪个版本,取决于在 pro29-module-x 中,对 pro30-module-ypro31-module-z 两个模块的依赖哪一个先声明。

在这里插入图片描述

8.1.2 抛异常:找不到方法

程序找不到符合预期的方法。这种情况多见于通过反射调用方法,所以经常会导致:java.lang.NoSuchMethodError

8.1.3 没报错但结果不对

发生这种情况比较典型的原因是:两个 jar 包中的类分别实现了同一个接口,这本来是很正常的。但是问题在于:由于没有注意命名规范,两个不同实现类恰巧是同一个名字。
在这里插入图片描述

具体例子是实际工作中遇到过:项目中部分模块使用 log4j 打印日志;其它模块使用 logback,编译运行都不会冲突,但是会引起日志服务降级,让你的 log 配置文件失效。比如:你指定了 error 级别输出,但是冲突就会导致 infodebug 都在输出。

8.2 本质

以上表现形式归根到底是两种基本情况导致的:

  • 同一 jar 包的不同版本

在这里插入图片描述

  • 不同 jar 包中包含同名类

这里我们拿 netty 来举个例子,Netty 是一个类似 Tomcat 的 Servlet 容器。通常我们不会直接依赖它,所以基本上都是框架传递进来的。那么当我们用到的框架很多时,就会有不同的框架用不同的坐标导入 Netty。可以参照下表对比一下两组坐标:

截止到 3.2.10.Final 版本以前的坐标形式从 3.3.0.Final 版本开始以后的坐标形式
org.jboss.netty netty 3.2.10.Finalio.netty netty 3.9.2.Final

但是偏偏这两个『不同的包』里面又有很多『全限定名相同』的类。例如:

org.jboss.netty.channel.socket.ServerSocketChannelConfig.class
org.jboss.netty.channel.socket.nio.NioSocketChannelConfig.class
org.jboss.netty.util.internal.jzlib.Deflate.class
org.jboss.netty.handler.codec.serialization.ObjectDecoder.class
org.jboss.netty.util.internal.ConcurrentHashMap$HashIterator.class
org.jboss.netty.util.internal.jzlib.Tree.class
org.jboss.netty.util.internal.ConcurrentIdentityWeakKeyHashMap$Segment.class
org.jboss.netty.handler.logging.LoggingHandler.class
org.jboss.netty.channel.ChannelHandlerLifeCycleException.class
org.jboss.netty.util.internal.ConcurrentIdentityHashMap$ValueIterator.class
org.jboss.netty.util.internal.ConcurrentIdentityWeakKeyHashMap$Values.class
org.jboss.netty.util.internal.UnterminatableExecutor.class
org.jboss.netty.handler.codec.compression.ZlibDecoder.class
org.jboss.netty.handler.codec.rtsp.RtspHeaders$Values.class
org.jboss.netty.handler.codec.replay.ReplayError.class
org.jboss.netty.buffer.HeapChannelBufferFactory.class
......

8.3 解决办法

很多情况下常用框架之间的整合容易出现的冲突问题都有人总结过了,拿抛出的异常搜索一下基本上就可以直接找到对应的 jar 包。我们接下来要说的是通用方法。

不管具体使用的是什么工具,基本思路无非是这么两步:

  • 第一步:把彼此冲突的 jar 包找到。
  • 第二步:在冲突的 jar 包中选定一个。具体做法无非是通过 exclusions 排除依赖,或是明确声明依赖。

8.3.1 IDEA 的 Maven Helper 插件

这个插件是 IDEA 中安装的插件,不是 Maven 插件。它能够给我们罗列出来同一个 jar 包的不同版本,以及它们的来源。但是对不同 jar 包中同名的类没有办法。
在这里插入图片描述

然后基于 pom.xml 的依赖冲突分析,如下:
在这里插入图片描述
查看冲突分析结果:

在这里插入图片描述

8.3.2 Maven 的 enforcer 插件

使用 Maven 的 enforcer 插件既可以检测同一个 jar 包的不同版本,又可以检测不同 jar 包中同名的类。

这里我们引入两个对 Netty 的依赖,展示不同 jar 包中有同名类的情况作为例子。

<dependencies>

    <dependency>
        <groupId>org.jboss.netty</groupId>
        <artifactId>netty</artifactId>
        <version>3.2.10.Final</version>
    </dependency>

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty</artifactId>
        <version>3.9.2.Final</version>
    </dependency>
    
</dependencies>

然后配置 enforcer 插件:

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <version>1.4.1</version>
                <executions>
                    <execution>
                        <id>enforce-dependencies</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>display-info</goal>
                            <goal>enforce</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>extra-enforcer-rules</artifactId>
                        <version>1.0-beta-4</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <rules>
                        <banDuplicateClasses>
                            <findAllDuplicates>true</findAllDuplicates>
                        </banDuplicateClasses>
                    </rules>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

执行如下 Maven 命令:

mvn clean package enforcer:enforce

部分运行结果:

在这里插入图片描述

  • 14
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
build-helper-maven-plugin:jar:1.8是一个用于构建Java项目的Maven插件。该插件提供了一些辅助功能,以帮助简化和优化项目构建过程。 首先,build-helper-maven-plugin:jar:1.8可以帮助我们在构建过程中处理一些非常规的任务。例如,它可以通过添加额外的源代码目录来扩展项目的结构,以便在构建过程中含其他源代码。这对于多模块项目或需要集成外部代码库的项目非常有用。 此外,该插件还提供了一些用于处理资源文件的功能。我们可以使用该插件来复制或移动资源文件到特定的目录,以便在构建过程中正确地含这些文件。这对于需要在构建期间处理和转换资源文件的项目非常有用,例如压缩JavaScript或CSS文件。 另一个重要的功能是build-helper-maven-plugin:jar:1.8可以帮助我们在构建过程中处理依赖关系。它可以自动将特定的依赖项添加到项目配置中,以便在编译和运行时正确地解决这些依赖项。这对于需要从其他模块或项目引入代码或库的项目非常有用。 除了以上提到的功能,build-helper-maven-plugin:jar:1.8还提供了其他一些辅助功能,如将构建生成的文件添加到构建输出中,生成版本号等。 综上所述,build-helper-maven-plugin:jar:1.8是一个功能强大的Maven插件,可以帮助我们更轻松地构建和管理Java项目。通过提供一些辅助功能,它提高了项目的灵活性和可维护性,使我们能够更加高效地进行构建过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

G皮T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值