Как загрузить модуль JDK программно? - PullRequest
0 голосов
/ 09 июня 2018

Предположим, у нас есть модуль A, который динамически загружает модуль B (используя классы ModuleFinder, ModuleLayer и т. Д.).Последний требует стандартного модуля java.sql, который не загружается в загрузочный слой с модулем A.Как загрузить требуемый java.sql из JDK (или JRE) с использованием Java-кода?

EDIT

Этот пример проекта 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 / 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 / module-info.java:

module app.module.loader {
    exports app.module.loader;
    uses AppModule;
}

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();
}

sql-module / 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-module / src / main / java / module-info.java:

module app.module.sql {
    requires app.module.loader;
    requires java.sql;

    provides AppModule with SQLAppModule;
}

sql-module / src / main / java / app / module / sql / SQLAppModule.java:

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";
    }
}

При попытке запустить приложение с помощью main в AppLauncher, вы получите сообщение об ошибке (сейчас я использую jdk-10.0.1):

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)

Как насчет хаков: Я думаю, что они должны использоваться в последнем месте, поэтому мы попробуемчтобы найти более или менее «официальный» способ сделать это сейчас.

Этот ответ не решает эту проблему, потому что layer.findModule(moduleName).orElse(null) при загрузке или любом другом слое вернет ноль.

1 Ответ

0 голосов
/ 01 февраля 2019

Вы не можете и не должны этого делать во время выполнения.

Возможно получить недостающие модули, необходимые для вашего модуля "app.module.sql":

var missingModuleNames = moduleFinder.find("app.module.sql")
                                     .map(ModuleReference::descriptor)
                                     .map(ModuleDescriptor::requires)
                                     .orElse(Collections.emptySet())
                                     .stream()
                                     .map(ModuleDescriptor.Requires::name)
                                     .filter(name -> boot.findModule(name).isEmpty())
                                     .collect(Collectors.toSet());

И вы даже можете создать ModuleFinder для модулей платформы Java:

var platformModules = Files.list(Paths.get(URI.create("jrt:/modules")))
                           .collect(Collectors
                               .toMap(AppLoader::getModuleName, Function.identity()));

var missingModulePaths = missingModules.stream()
                                       .filter(systemModules::containsKey)
                                       .map(systemModules::get)
                                       .toArray(Path[]::new);

var missingModuleFinder = ModuleFinder.of(missingModulePaths);

Но даже если вы делаете это рекурсивно (java.sql требует java.transaction.xa), ваша попытка загрузить любой из модулей платформызавершится с LayerInstantiationException, как только вы попытаетесь определить модули:

var cfg = boot.configuration()
              .resolveAndBind(missingModuleFinder, ModuleFinder.of(), missingModules);

// This will throw the exception, because 'a layer cannot be created if the 
// configuration contains a module named "java.base", or a module contains a 
// package named "java" or a package with a name starting with "java.".'
// (see Javadoc of ModuleLayer#defineModulesWithOneLoader(Configuration, List<ModuleLayer>, ClassLoader)
ModuleLayer.defineModulesWithOneLoader(cfg, List.of(boot), null);
...