类加载器 + 模块 = 令人头疼的测试
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 确实已导出。
我已经尝试了很多东西,例如:
- 声明(已经导出的)包也对 Core 开放。
- 将上面列出的所有模块标记为
open module
。
- 将
dependencies { testRuntimeOnly project(':API') }
添加到 Core 的 build.gradle 文件。
- 有趣的是,
runtime project(':API')
造成了循环依赖。
- 扯掉一大把头发
任何人都可以解释这个混乱的原因吗?这显然是一个 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 的实例(据我了解,这就是你想要的……)。
当我遇到类似的问题时,我是这样解决的:
- 我在 Core 中定义了已经提到的接口。
- 接下来,我定义了一个 class
AnInterfaceFactory
,它为 Core 中的 AnInterface
个实例采用 Supplier
的一个实例(或者我将该参数添加到构造函数中 class 需要创建新的 AnInterface
实例,或者我将该参数添加到相应的方法中……)
- 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 的上下文中,但对它的引用可以传递到任何地方……
我有三个模块,我们称它们为 "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 确实已导出。
我已经尝试了很多东西,例如:
- 声明(已经导出的)包也对 Core 开放。
- 将上面列出的所有模块标记为
open module
。 - 将
dependencies { testRuntimeOnly project(':API') }
添加到 Core 的 build.gradle 文件。- 有趣的是,
runtime project(':API')
造成了循环依赖。
- 有趣的是,
- 扯掉一大把头发
任何人都可以解释这个混乱的原因吗?这显然是一个 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 的实例(据我了解,这就是你想要的……)。
当我遇到类似的问题时,我是这样解决的:
- 我在 Core 中定义了已经提到的接口。
- 接下来,我定义了一个 class
AnInterfaceFactory
,它为 Core 中的AnInterface
个实例采用Supplier
的一个实例(或者我将该参数添加到构造函数中 class 需要创建新的AnInterface
实例,或者我将该参数添加到相应的方法中……) - 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 的上下文中,但对它的引用可以传递到任何地方……