bigywater.tech
Java · 274

Classpath 与 Module Path:依赖可见性为什么会变化

依赖放在 classpath 还是 module path 上,可见性会变:哪些包看得到、能不能反射、同名包怎么处理。这篇用同一个小库放在不同路径上逐项真实运行,包括自动模块的命名规则和 JDK 内部 API 的访问限制。

unnamed module → module liba
同一个库从 classpath 移到 module path 后所在模块的变化
opens
反射私有字段需要的模块声明;只有 exports 不够
NoClassDefFoundError
v1 在 module path、v2 在 classpath 且共享同一个包时,只在 v2 里的类的结果

可见性:classpath 与 module path

实测 · 本机执行输出
  编译 Direct(直接引用 pkg.internal.Secret),liba 在 classpath 上 → 返回码 0 (classpath 上所有包都可见)
  同样的源码,liba(有 module-info,只导出 pkg.api)在 module path 上 → 返回码 1 ; ['app2/Direct.java:1: error: package pkg.internal is not visible']
  运行 Main,liba 在 classpath 上(未命名模块):
    Api.hi() = api,Api 的模块 = unnamed module @6bc7c054,是否命名模块 = false
    Class.forName("pkg.internal.Secret") 成功,所在模块 unnamed module @6bc7c054
    反射调用公开类的公开构造器成功
    setAccessible(true) 读私有字段成功:private-field
    反射调用公开类的 public 静态方法 → internal-value
  运行 Main,liba 在 module path 上(命名模块 liba,只导出 pkg.api):
    Api.hi() = api,Api 的模块 = module liba,是否命名模块 = true
    Class.forName("pkg.internal.Secret") 成功,所在模块 module liba
    反射 newInstance → IllegalAccessException: class Main cannot access class pkg.internal.Secret (in module liba) because module liba does not export pkg.internal to unnamed module @433c675d
    读私有字段 → InaccessibleObjectException: Unable to make field private java.lang.String pkg.internal.Secret.hidden accessible: module liba does not "opens pkg.internal" to unnamed module @433c675d
    反射调用 public 静态方法 → IllegalAccessException: class Main cannot access class pkg.internal.Secret (in module liba) because module liba does not export pkg.internal to unnamed module @433c675d
  再加 --add-opens liba/pkg.internal=ALL-UNNAMED:
    setAccessible(true) 读私有字段成功:private-field
  只加 --add-exports liba/pkg.internal=ALL-UNNAMED(导出但不 opens):
    读私有字段 → InaccessibleObjectException: Unable to make field private java.lang.String pkg.internal.Secret.hidden accessible: module liba does not "opens pkg.internal" to unnamed module @5197848c

自动模块

实测 · 本机执行输出
  jar --describe-module liba-1.2.jar: No module descriptor found. Derived automatic module. |  | liba@1.2 automatic | requires java.base mandated | contains pkg.api | contains pkg.internal
  java --module-path liba-1.2.jar --describe-module liba(自动模块名由文件名 liba-1.2.jar 推出:去掉版本号 -1.2): liba@1.2 file://<jtmp>/j134w/liba-1.2.jar automatic | requires java.base mandated | contains pkg.api | contains pkg.internal
  运行 Main,liba-1.2.jar(没有 module-info)在 module path 上:
    Api.hi() = api,Api 的模块 = module liba,是否命名模块 = true
    Class.forName("pkg.internal.Secret") 成功,所在模块 module liba
    反射调用公开类的公开构造器成功
    setAccessible(true) 读私有字段成功:private-field
    反射调用公开类的 public 静态方法 → internal-value
  加了 Automatic-Module-Name: com.example.liba 之后: com.example.liba@1.2-am file://<jtmp>/j134w/liba-1.2-am.jar automatic | requires java.base mandated | contains pkg.api | contains pkg.internal
  文件名 my-lib-3.0.1-SNAPSHOT.jar 推出的模块名: my.lib@3.0.1-SNAPSHOT file://<jtmp>/j134w/my-lib-3.0.1-SNAPSHOT.jar automatic | requires java.base mandated
  文件名 123-lib.jar(以数字开头)→ Error occurred during initialization of boot layer | java.lang.module.FindException: Unable to derive module descriptor for 123-lib.jar

split package

实测 · 本机执行输出
  classpath 顺序 v1.jar:v2.jar:
    Util.v() = v1;Util 来自 v1.jar
    Extra.e() = extra-only-in-v2
  classpath 顺序 v2.jar:v1.jar:
    Util.v() = v2;Util 来自 v2.jar
    Extra.e() = extra-only-in-v2
  两个 jar 放在 module path 上(都是自动模块 v1 与 v2,同一个包 pkg.same):返回码 1 ; Error occurred during initialization of boot layer | java.lang.module.ResolutionException: Module v2 contains package pkg.same, module v1 exports package pkg.same to v2
  v1 在 module path 上、v2 在 classpath 上:
    Util.v() = v1;Util 来自 v1.jar
    Extra → NoClassDefFoundError

JDK 内部 API 的封装

实测 · 本机执行输出
  [JDK 17,默认]
    读 String.value → InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @10742304
    jdk.internal.misc.Unsafe → IllegalAccessException: class Priv cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @10742304
    sun.misc.Unsafe.theUnsafe(jdk.unsupported 模块):成功,类型 sun.misc.Unsafe
  [JDK 17,--add-opens java.base/java.lang=ALL-UNNAMED]
    读 String.value:成功(byte[])
    jdk.internal.misc.Unsafe → IllegalAccessException: class Priv cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @4d7e1886
    sun.misc.Unsafe.theUnsafe(jdk.unsupported 模块):成功,类型 sun.misc.Unsafe
  [JDK 17,--add-exports java.base/jdk.internal.misc=ALL-UNNAMED]
    读 String.value → InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @4d7e1886
    jdk.internal.misc.Unsafe.getUnsafe() 调用:jdk.internal.misc.Unsafe
    sun.misc.Unsafe.theUnsafe(jdk.unsupported 模块):成功,类型 sun.misc.Unsafe
  [JDK 21,默认]
    读 String.value → InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @15db9742
    jdk.internal.misc.Unsafe → IllegalAccessException: class Priv cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @15db9742
    sun.misc.Unsafe.theUnsafe(jdk.unsupported 模块):成功,类型 sun.misc.Unsafe
  [JDK 21,--add-opens java.base/java.lang=ALL-UNNAMED]
    读 String.value:成功(byte[])
    jdk.internal.misc.Unsafe → IllegalAccessException: class Priv cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @7adf9f5f
    sun.misc.Unsafe.theUnsafe(jdk.unsupported 模块):成功,类型 sun.misc.Unsafe
  [JDK 21,--add-exports java.base/jdk.internal.misc=ALL-UNNAMED]
    读 String.value → InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @7adf9f5f
    jdk.internal.misc.Unsafe.getUnsafe() 调用:jdk.internal.misc.Unsafe
    sun.misc.Unsafe.theUnsafe(jdk.unsupported 模块):成功,类型 sun.misc.Unsafe

分步演示

Classpath 与 Module Path
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 17.0.20 与 JDK 21.0.12;示例库 liba 是自己写的最小模块
    • 没有验证:ServiceLoader 在两种路径下的差别、Maven/Gradle 对 module path 的配置、模块图里 requires transitive 与 requires static 的细节、--patch-module、多模块应用的 jlink、反射的 opens 声明写在 module-info 里(open module)的方式
    京ICP备2026067502号-1京公网安备11010502063487号