类加载器 + 模块 = 令人头疼的测试

Classloader + Modules = testing headaches

我有三个模块,我们称它们为 "Core"、"APIs" 和 "API"。

我需要做什么才能使 API 中的 class 对 Core 中的 ClassLoader 代码可见?

核心:

module Core {
   //requires nothing relevant... org.json, commons.math3
   exports Core.foo;
}

API:

module API {
   requires Core;
   exports API.bar;
}

APIs:

module APIs {
  requires transitive Core;
  requires transitive API;

  exports APIs.baz;
}

APIs 调用一个 Core 函数,该函数试图通过 API 通过 Class.forName("API.bar.someClass") 按名称获取 class,并立即抛出 ClassNotFound 异常.请注意 API.bar 确实已导出。

我已经尝试了很多东西,例如:

任何人都可以解释这个混乱的原因吗?这显然是一个 class path/module 路径问题,但我的印象是传递和exports 应该处理所有这些,并且所有这些 jar 文件都 运行 在同一个 java.exe 具有共享模块路径的会话中......所以没问题吧?显然不是。

解决这个问题的方法是什么?

如果没有更多详细信息 and/or 示例代码,很难说出您面临的确切问题。我已经尝试根据问题中的描述创建一个最小的、完整的示例项目,并且我在构建和 运行 应用程序时没有任何问题。希望这个示例项目能帮助您走上正轨。

项目结构

.
├── api
│   └── src
│       └── main
│           └── java
│               ├── api
│               │   └── bar
│               │       └── MyApi.java
│               └── module-info.java
├── apis
│   └── src
│       └── main
│           └── java
│               ├── apis
│               │   └── baz
│               │       └── MyCoreUser.java
│               └── module-info.java
├── build.gradle
├── core
│   └── src
│       └── main
│           └── java
│               ├── core
│               │   └── foo
│               │       └── MyApiReflection.java
│               └── module-info.java
└── settings.gradle

(为简洁起见,这不显示任何 Gradle Wrapper 个文件。)

项目来源

settings.gradle

include 'core', 'api', 'apis'

build.gradle

plugins {
    id 'org.javamodularity.moduleplugin' version '1.6.0' apply false
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'org.javamodularity.moduleplugin'
}

// only needed if you keep "requires core" in module-info.java:
/*
project(':api') {
    dependencies {
        implementation project(':core')
    }
}
*/

project(':apis') {
    dependencies {
        implementation project(':core')
        implementation project(':api')
    }
}

api/src/main/java/api/bar/MyApi.java

package api.bar;

public class MyApi {

    public String getHello() {
        return "hello";
    }
}

api/src/main/java/module-info.java

module api {
    // seemingly not required but would work, too (see also note in build.gradle):
    //requires core;

    exports api.bar;
}

apis/src/main/java/apis/baz/MyCoreUser.java

package apis.baz;

import core.foo.MyApiReflection;

public class MyCoreUser {

    public static void main(String... args) throws Exception {
        System.out.println(MyApiReflection.callMyApi());
    }
}

apis/src/main/java/module-info.java

module apis {
    requires transitive core;
    requires transitive api;

    exports apis.baz;
}

core/src/main/java/core/foo/MyApiReflection.java

package core.foo;

public class MyApiReflection {

    public static String callMyApi() throws Exception {
        Class<?> clazz = Class.forName("api.bar.MyApi");
        Object instance = clazz.newInstance();
        return (String) clazz.getMethod("getHello").invoke(instance);
    }
}

core/src/main/java/module-info.java

module core {
    exports core.foo;
}

如何Build/Run

$ ./gradlew build
…
BUILD SUCCESSFUL in 1s
6 actionable tasks: 6 executed

$ java \
    --module-path apis/build/libs/apis.jar:core/build/libs/core.jar:api/build/libs/api.jar \
    --module apis/apis.baz.MyCoreUser
hello

我已经用 Gradle 6.0.1 和 Java 11 测试过了。

根据我的理解,依赖项只会沿着树向下:APIs 需要 API 需要核心。

这意味着 Core 不知道(也不应该知道)关于 API 和 APIs 的任何信息。

如果 Core 应该操作 API 中定义的 Class 的实例,Core 必须提供一个由 class 实现的接口 AnInterface API:

public class AClass implements AnInterface {…}

然后你可以在 Core 中使用一个方法来获取 AnInterface:

的实例
public class CoreClass
{
    public aMethod( AnInterface param ) {…}
}

但这意味着 Core 中的方法无法创建 API 中定义的 classes 的实例(据我了解,这就是你想要的……)。

当我遇到类似的问题时,我是这样解决的:

  1. 我在 Core 中定义了已经提到的接口。
  2. 接下来,我定义了一个 class AnInterfaceFactory,它为 Core 中的 AnInterface 个实例采用 Supplier 的一个实例(或者我将该参数添加到构造函数中 class 需要创建新的 AnInterface 实例,或者我将该参数添加到相应的方法中……)
  3. API 中的实现 class 为 class 的实例提供了一个简单的静态工厂方法,与 Supplier 接口兼容。

可能看起来像这样:

在核心:

public final CoreClass
{
    …
    public final AnInterface createAnInterfaceInstance( Supplier<AnInterface> factory )
    { return factory.get(); }
}

在API中:

public class AClass implements AnInterface
{
    public final static AClass create() { return new AClass(); }
}

在(例如)APIs:

…
var instance = new CoreClass().createAnInterfaceInstance( AClass::create );
…

之所以可行,是因为方法 create() 存在于模块 API 的上下文中,但对它的引用可以传递到任何地方……