likes
comments
collection
share

Java 9 之后 解决多个模块包含相同包名的问题

作者站长头像
站长
· 阅读数 9

最近接手一个新的Java项目, 架构师明确要Java 11 版本, 于是在 eclipse 里面设置使用JDK 17, JDK 编译器兼容 level 设置成 11. 设置完成后, 然后重新编译, 竟然报错了, 还是几百个错误!

错误内容全部是: The package java.util is accessible from more than one module: <unnamed>, java.base.

如下图, 这些从java.util引用的类, 全部都报这个错: Java 9 之后 解决多个模块包含相同包名的问题

这个错误代表什么意思

Java 9 开始有了模块化系统的概念(JPMS), 每个Java 文件都属于某个模块. 例如上面例子中出现的 java.util.HashMap, java.util.concurrent.TimeUnit 都属于JDK里面的 java.base 模块.

那么<unnamed> 模块是什么呢?

对于JDK本身, 它默认已经分了模块, 但是我们还有很多jar包, 并没有按照模块的方式去打包, 或者在JDK 9 之前就有了这些jar包, 我们照样可以依赖它们.

对各种模块的依赖, 会加到modulepath, 对于非模块的依赖, 继续使用classpath. classpath上的类会被统一的分配到一个叫<unnamed>的模块.

如下面的 eclipse 截图所示, 所有maven的依赖全部放到了classpath.

Java 9 之后 解决多个模块包含相同包名的问题

错误含义

所以, 这个错误的真正含义是: 在我们依赖中, java.util包即出现在了 JDK 的 java.base 这个模块, 也出现在了我们依赖的classpath上. 而Java模块化系统不允许同一个包出现在2个模块里面.

如何修复

有2种可能的方案:

  1. 退回到Java 8, 不使用模块化的概念, 错误消失.
  2. 找到提供java.util包的jar包, 质问开发者为啥给自己的jar包加入jdk的包名, 是想夹带私货吗?

我们这里只能选择第二个选项, 所以如何找到这个包含java.util的jar包成了重点.

如何找到第二个包含java.util的jar包

看到最初错误截图里面那些类, 我们很直观的可能认为: 只要使用eclipse的 open type 菜单是不是就很容易找到了, 我们试一下:

Java 9 之后 解决多个模块包含相同包名的问题

从上面截图可以看到, eclipse 只找到一个HashMap, 并且源自于JDK11, 同样, 对于java.uti.Map, java.util.concurrent.TimeUnit 我们也只能找到仅有的一个属于JDK的.

明明报了java.util包来自2个模块, 为啥我们只找到一个呢?

其实, 我们上面的方式去找HashMap这个类出现在哪个包, 但是这个错误的真正的重点是包的名字, 也就是说, 有另外一个jar包里包含了java.util.Xxxx, 它不管类名是什么, 只管包名和JDK里面的java.util包重复了.

我们可以同样的方式在 eclipse 的 open type 里面输入java.util.*, 只不过会出现非常长的列表, 然后人工一个个对比.

如何手工找到这个jar包

这里提供2个方法:

  1. 方法一: 使用 eclipse open type, 然后输入 java.util.*,如下图, 按顺序找一定能找到: Java 9 之后 解决多个模块包含相同包名的问题

  2. 方法二: 在 maven 的pom.xml 里面去掉一部分依赖, 看是不是这个错误消失了(同时其它依赖缺失错误会出现), 如果这个消失了, 说明去掉的这个依赖里面(直接或间接)包含这个包名. 使用一次去掉多个依赖(二分查找)可以加快速度.

如何自动化找到这个jar

eclipse 能报出这个错误, 说明它在我们的依赖中, 除了JDK之外还有一个jar包含这个包. 那么, 我们可以用同样的方法, 找到这个jar.

步骤:

  1. 通过 mvn 命令找到所有的依赖jar.
  2. 查找 jar 里面的类全名是不是包含 java.util.

mvn 命令找到所有的依赖jar

首先, 我们进入的项目pom.xml 所在的目录, 然后执行mvn 的goal: dependency:build-classpath, 把输出结果全部放到/tmp/dep.txt文件.

$ cd <project_dir>
$ ~/work/tools/apache-maven-3.9.1/bin/mvn dependency:build-classpath -Dmdep.outputFile=/tmp/dep.txt

打开/tmp/dep.txt文件, 可以看到里面是以:(MAC上)分割开的jar文件名.

遍历查找每个jar的类

第二步, 读取上面的输出, 以:分隔成多个jar, 然后遍历每个jar, 找出包含java.util的类,

    jars=(${(s/:/)$(cat /tmp/dep.txt)})
    for i in "${jars[@]}" 
    do 
        jar -tf $i | grep '^java.util' && echo "\t\tfind in ${i}"
    done

执行上面的脚本(mac 上的zsh), 得到如下结果:

java/util/
java/util/concurrent/
java/util/Hashtable.class
java/util/concurrent/ConcurrentHashMap.class
		find in /Users/eric/.m2/repository/com/boundary/high-scale-lib/1.0.6/high-scale-lib-1.0.6.jar

最终, 通过几行shell命令, 我们也找到了包含这个包名的jar包.

总结

Java 9 之后, 开始分模块, 一个项目可能既有对模块的依赖, 还有对普通jar包的依赖, 这些以前的jar包会被放到一个<unnamed>的模块. 如果一个包名出现在多个模块中(包含<unnamed>的模块), 就会出现这个错.

本文中另外一个出现在<unnamed>的模块, 可以通过遍历查找依赖jar的方式去查找到这个jar包.

JDK 里面有哪些模块?

java --list-modules