Java 如何以编程方式加载JDK模块?

Java 如何以编程方式加载JDK模块?,java,java-module,Java,Java Module,假设我们有一个模块A,它动态加载模块B(使用类ModuleFinder,ModuleLayer等)。最后一个需要标准模块java.sql,该模块未使用模块A加载到启动层。如何使用java代码从JDK(或JRE)加载所需的java.sql 编辑 此示例maven项目演示了我的问题: 项目结构: │ pom.xml │ ├───loader │ │ pom.xml │ │ │ └───src │ ├───main │ │ ├───java │

假设我们有一个模块
A
,它动态加载模块
B
(使用类
ModuleFinder
ModuleLayer
等)。最后一个需要标准模块
java.sql
,该模块未使用模块
A
加载到启动层。如何使用java代码从JDK(或JRE)加载所需的
java.sql

编辑

此示例maven项目演示了我的问题:

项目结构:

│   pom.xml
│
├───loader
│   │   pom.xml
│   │
│   └───src
│       ├───main
│       │   ├───java
│       │   │   │   module-info.java
│       │   │   │
│       │   │   └───app
│       │   │       └───module
│       │   │           └───loader
│       │   │                   AppLoader.java
│       │   │                   AppModule.java
│       │   │
│       │   └───resources
│       └───test
│           └───java
└───sql-module
    │   pom.xml
    │
    └───src
        ├───main
        │   ├───java
        │   │   │   module-info.java
        │   │   │
        │   │   └───app
        │   │       └───module
        │   │           └───sql
        │   │                   SQLAppModule.java
        │   │
        │   └───resources
        └───test
            └───java
pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>sample-app</artifactId>
        <groupId>sample-app</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>loader</artifactId>
</project>
loader/src/main/java/app/module/loader/AppLoader.java:

public class AppLoader {
    public static void main(String[] args) {
        var path = Paths.get("sql-module", "target", "classes");
        var moduleFinder = ModuleFinder.of(path);
        var boot = ModuleLayer.boot();
        var config = boot.configuration().resolveAndBind(moduleFinder, ModuleFinder.of(), Collections.emptyList());
        var newLayer = boot.defineModulesWithOneLoader(config, Thread.currentThread().getContextClassLoader());
        var testModule = ServiceLoader.load(newLayer, AppModule.class)
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Module not found!"));
        System.out.println("Module name: " + testModule.name());
        System.out.println("Module version: " + testModule.version());
    }
}
loader/src/main/java/app/module/loader/AppModule.java:

public interface AppModule {
    String name();
    String version();
}
public class SQLAppModule implements AppModule {
    public SQLAppModule() {
        List<Driver> drivers = DriverManager.drivers().collect(Collectors.toList());
        System.out.println("Drivers on class path: " + drivers.size());
        drivers.forEach(d -> {
            System.out.println("Driver: " + d.toString());
            System.out.println("Version: " + d.getMajorVersion() + "." + d.getMinorVersion());
        });
    }

    @Override
    public String name() {
        return "SQL Module";
    }

    @Override
    public String version() {
        return "1.0-SNAPSHOT";
    }
}
sql模块/pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>sample-app</artifactId>
        <groupId>sample-app</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>sql-module</artifactId>
    <dependencies>
        <dependency>
            <groupId>sample-app</groupId>
            <artifactId>loader</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
sql模块/src/main/java/app/module/sql/SQLAppModule.java:

public interface AppModule {
    String name();
    String version();
}
public class SQLAppModule implements AppModule {
    public SQLAppModule() {
        List<Driver> drivers = DriverManager.drivers().collect(Collectors.toList());
        System.out.println("Drivers on class path: " + drivers.size());
        drivers.forEach(d -> {
            System.out.println("Driver: " + d.toString());
            System.out.println("Version: " + d.getMajorVersion() + "." + d.getMinorVersion());
        });
    }

    @Override
    public String name() {
        return "SQL Module";
    }

    @Override
    public String version() {
        return "1.0-SNAPSHOT";
    }
}
那黑客呢:我认为他们必须在最后一个地方使用,所以我们现在尝试找到或多或少的“官方”方式来做


无法解决此问题,因为启动时或任何其他层上的layer.findModule(moduleName).orElse(null)将返回null。

您不能也不应该在运行时执行此操作

可以获取“app.module.sql”模块所需的缺失模块:

var missingModuleNames=moduleFinder.find(“app.module.sql”)
.map(模块引用::描述符)
.map(ModuleDescriptor::requires)
.orElse(Collections.emptySet())
.stream()
.map(ModuleDescriptor.Requires::name)
.filter(name->boot.findModule(name).isEmpty())
.collect(收集器.toSet());
您甚至可以为Java平台模块创建一个
ModuleFinder

var platformModules=Files.list(path.get(URI.create(“jrt:/modules”))
.收藏(收藏家)
.toMap(AppLoader::getModuleName,Function.identity());
var missingModulePaths=missingModules.stream()
.filter(systemModules::containsKey)
.map(systemModules::get)
.toArray(路径[]:新建);
var missingModuleFinder=模块Finder.of(MissingModulePath);
但是,即使您执行此递归操作(
java.sql
要求
java.transaction.xa
),只要您尝试定义模块,加载任何平台模块的尝试都将失败,并出现
LayerInstationException

var cfg=boot.configuration()
.resolveAndBind(missingModuleFinder,ModuleFinder.of(),missingModules);
//这将引发异常,因为“如果
//配置包含名为“java.base”的模块,或者模块包含
//名为“java”的包或名称以“java”开头的包
//(参见ModuleLayer的Javadoc#defineModulesWithOneLoader(配置、列表、类加载器)
ModuleLayer.defineModulesWithOneLoader(cfg,List.of(boot)),null;

google展示了这一点,但它看起来像一个黑客。你可以简单地使用一个导入语句……这就是它们构建的目的。通常不需要按时间顺序加载库。你能解释一下你的场景吗?通常Java处理导入,并在不需要的时候加载/卸载它们。对不起,黑客解决方案就是这样被称为reaso的n、 它们通常很难理解,而且通常不是很健壮。黑客是你的最后手段,作为专业人士,你尽可能避免使用。gist上的代码片段确实是一个黑客,因为它正在侵入JDK内部API(使用
setAccessible(true)
调用不可见方法)。Java模块的全部目的是阻止这样的代码工作。它目前在JDK 9/10中工作,因为默认选项是
--invalize access=permit
,但在将来的某个时候(JDK 11或更高版本),这种方法将停止工作。看起来更像是
public class SQLAppModule implements AppModule {
    public SQLAppModule() {
        List<Driver> drivers = DriverManager.drivers().collect(Collectors.toList());
        System.out.println("Drivers on class path: " + drivers.size());
        drivers.forEach(d -> {
            System.out.println("Driver: " + d.toString());
            System.out.println("Version: " + d.getMajorVersion() + "." + d.getMinorVersion());
        });
    }

    @Override
    public String name() {
        return "SQL Module";
    }

    @Override
    public String version() {
        return "1.0-SNAPSHOT";
    }
}
Exception in thread "main" java.lang.module.FindException: Module java.sql not found, required by app.module.sql
    at java.base/java.lang.module.Resolver.findFail(Resolver.java:877)
    at java.base/java.lang.module.Resolver.resolve(Resolver.java:191)
    at java.base/java.lang.module.Resolver.bind(Resolver.java:297)
    at java.base/java.lang.module.Configuration.resolveAndBind(Configuration.java:482)
    at java.base/java.lang.module.Configuration.resolveAndBind(Configuration.java:288)
    at app.module.loader/app.module.loader.AppLoader.main(AppLoader.java:14)