Как загрузить модуль JDK программно?
Предположим, у нас есть модуль A
динамически загружает модуль B
(используя классы ModuleFinder
, ModuleLayer
, так далее). Последний требует стандартного модуля java.sql
который не загружается в загрузочный слой с модулем A
, Как загрузить требуется java.sql
из JDK (или JRE) с использованием кода Java?
РЕДАКТИРОВАТЬ
Этот пример проекта 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>
Загрузчик / 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>
Загрузчик / SRC / главная / Java / module-info.java:
module app.module.loader {
exports app.module.loader;
uses AppModule;
}
Загрузчик / SRC / главная / Java / приложение / модуль / загрузки / 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());
}
}
Загрузчик / SRC / главная / Java / приложение / модуль / загрузки / AppModule.java:
public interface AppModule {
String name();
String version();
}
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 / главная / Java / module-info.java:
module app.module.sql {
requires app.module.loader;
requires java.sql;
provides AppModule with SQLAppModule;
}
SQL-модуль / SRC / главная / Java / приложение / модуль / 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";
}
}
При попытке запустить приложение, используя основной в 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)
при загрузке или любой другой слой вернет ноль.
0 ответов
Вы не можете и не должны делать это во время выполнения.
Можно получить недостающие модули, необходимые для вашего модуля "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);