Мы разрабатываем приложение JavaFX 11 для Adopt JDK 11 и IntelliJ с использованием Gradle.В конце нам нужен файл EXE для Windows (приложение предназначено только для Windows).В первой попытке мы также использовали launch4J в Gradle, из-за следующей проблемы нам также было бы хорошо использовать BAT-файл, который мы могли бы вместо этого перенести в исполняемый файл.
Итак, наша главная цель и вопрос в том, как создать исполняемый файл JAR.
Мы сделали несколько подходов с разными результатами, и мы полностью потеряны.
FAT JAR
Мы сделали отдельное приложение для тестирования:
package de.test;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
VBox vBox = new VBox();
Button button = new Button("Klick");
TextField textfield = new TextField();
TextArea area = new TextArea();
area.setMinHeight(300);
button.setOnAction(event -> area.setText(area.getText() + " -- Klick Version 1.0.8"));
vBox.getChildren().addAll(button, textfield,area);
primaryStage.setScene(new Scene(vBox));
primaryStage.setTitle("Test");
primaryStage.show();
}
}
FAT JAR
И нашей первой попыткой был FAT-JAR с или без файла modules-info.java с использованием плагина javafx из gradle.Вот наш файл gradle
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
group 'de.test'
version '1.0.8'
sourceCompatibility = 11
javafx {
modules = ['javafx.controls']
}
mainClassName = 'de.test.Main'
jar {
manifest {
attributes 'Main-Class': mainClassName
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
repositories {
mavenLocal()
mavenCentral()
}
После запуска gradle clean jar мы попытались выполнить jar с помощью следующей команды java -jar ApplicationTest-1.0.8.jar результат:
Ошибка: не удалось найти или загрузить основной класс de.test.Main. Причина: java.lang.NoClassDefFoundError: javafx / application / Application
FAT JAR с зависимостями и module-info.java
Затем мы попытались добавить зависимости для JavaFX следующим образом в файл gradle
compile group: 'org.jboss.resteasy', name: 'resteasy-client', version: '4.3.0.Final'
compile "org.openjfx:javafx-graphics:11.0.2:win"
compile "org.openjfx:javafx-base:11.0.2:win"
compile "org.openjfx:javafx-controls:11.0.2:win"
compile "org.openjfx:javafx-fxml:11.0.2:win"
compile "org.openjfx:javafx-graphics:11.0.2:win"
И это информация о модуле.java-дескриптор, который мы добавили в пакет de.test:
module ApplicationTest.main {
requires javafx.controls;
exports de.test;
}
При вызове gradle installDist мы использовали эту команду
cd build \ install \ ApplicationTest\ lib java --адд-модули "javafx.controls" --модуль-путь.-jar ApplicationTest-1.0.8.jar
По крайней мере, приложение запустилось в этот момент, но макет был разрушен со следующими информационными сообщениями:
Сен.20, 2019 8:32:55 ВОРМ.com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged INFO: Не удалось загрузить таблицу стилей: com / sun / javafx / scene / control / skin / modena / modena.css 20 сентября 2019 г. 8:32:55 VORM.com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged INFO: не удалось загрузить таблицу стилей: com / sun / javafx / scene / control / skin / modena / modena.css
Между прочим: IntelliJ всегда работает при использовании команды gradle clean run
JLink с module-info.java
Следующим подходом было использование jlink и следующих модулей-info.java
module ApplicationTest.main {
requires javafx.controls;
requires javafx.graphics;
exports de.test;
}
Нам нужно было немного расширить файл gradle, так какследующие
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
id 'org.beryx.jlink' version '2.10.4'
}
group 'de.test'
version '1.0.8'
sourceCompatibility = 11
mainClassName = 'de.test.Main'
javafx {
version = 11
modules = [ 'javafx.controls']
}
jlink {
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}
repositories {
mavenLocal()
mavenCentral()
}
При использовании gradle jlink и выполнении файла cd build \ image \ bin \ ApplicationTest.bat приложение запускается WITH макет.
В этот момент мы где-то счастливы.Теперь нам нужно было добавить некоторые зависимости для нашего приложения.
Использование следующих зависимостей прекрасно работало
dependencies {
compile "commons-io:commons-io:2.6"
compile "org.apache.logging.log4j:log4j-api:2.11.2"
compile "dom4j:dom4j:1.6.1"
compile "commons-lang:commons-lang:2.6"
compile "axis:axis:1.4"
compile "jaxen:jaxen:1.1.6"
compile "net.java.dev.jna:platform:3.5.2"
compile "org.apache.poi:poi:4.1.0"
compile "org.apache.poi:poi-scratchpad:4.1.0"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4"
}
Но как только я добавляю другие зависимости, я получаю сообщения об ошибках другого типа.Я пробовал каждую зависимость в одиночку, чтобы изолировать сообщение об ошибке.
org.jboss.resteasy - resteasy-client - 3.7.0.Final
Cannot derive uses clause from service loader invocation in: javax/ws/rs/client/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/ws/rs/ext/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/ws/rs/sse/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/xml/bind/ServiceLoaderUtil.firstByServiceLoader().
ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:154: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
provides javax.ws.rs.ext.Providers with org.jboss.resteasy.plugins.interceptors.CacheControlFeature,
^
ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:155: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
org.jboss.resteasy.plugins.interceptors.encoding.ClientContentEncodingAnnotationFeature,
^
ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:156: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
org.jboss.resteasy.plugins.interceptors.encoding.MessageSanitizerContainerResponseFilter,
^
...
^
25 errors
Зависимость: org.apache.logging.log4j: log4j-core: 2.11.2
package org.apache.logging.log4j.spi is not visible
(package org.apache.logging.log4j.spi is declared in module org.apache.logging.log4j, but module de.test.merged.module does not read it)
package org.apache.logging.log4j.message is not visible
(package org.apache.logging.log4j.message is declared in module org.apache.logging.log4j, but module de.test.merged.module does not read it)
Зависимость: org.jboss.resteasy: resteasy-multipart-provider: 3.7.0.Final
module not found: java.activation
Зависимость: org.jboss.resteasy: resteasy-jackson2-provider: 3.7.0.Final
package javax.ws.rs.ext does not exist
package javax.ws.rs.ext does not exist
package javax.ws.rs.ext does not exist
Заключение
Мы сейчас никуда не денемся.Возможно, это как-то связано с modules-info.java, но инструкции и документация по этой теме довольно сложны и часто очень подробны.Мы просто хотели приложение, которое можно было запускать за пределами IntelliJ.
Мы открыты для всех решений, пока они работают.Нам также не нужно современное решение, но что-то, из чего мы можем каким-то образом получить EXE-файл (также с помощью внешнего инструмента).Наше приложение не обязательно должно быть модульным, но мы не получаем и не решаем его.
РЕДАКТИРОВАТЬ 1) FAT Jar со вторым основным классом
Как и в этой статье мы добавили второй основной Java-класс без расширения Application и поместили его в файл Gradle
package de.test;
public class Main {
public static void main(String[] args) {
MainFX.main(args);
}
}
, а другой класс выглядит так:
package de.test;
import ...;
public class MainFX extends Application {
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
...
}
}
ифайл Gradle выглядит следующим образом:
plugins {
id 'java'
id 'application'
}
group 'de.test'
version '1.0.8'
sourceCompatibility = 11
mainClassName = 'de.test.MainFX'
jar {
manifest {
attributes 'Main-Class': mainClassName
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile "commons-io:commons-io:2.6"
compile "org.apache.logging.log4j:log4j-api:2.11.2"
compile "dom4j:dom4j:1.6.1"
compile "commons-lang:commons-lang:2.6"
compile "axis:axis:1.4"
compile "jaxen:jaxen:1.1.6"
compile "net.java.dev.jna:platform:3.5.2"
compile "org.apache.poi:poi:4.1.0"
compile "org.apache.poi:poi-scratchpad:4.1.0"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4"
compile "org.jboss.resteasy:resteasy-multipart-provider:3.7.0.Final"
compile "org.jboss.resteasy:resteasy-jackson2-provider:3.7.0.Final"
compile "org.apache.logging.log4j:log4j-core:2.11.2"
compile "org.jboss.resteasy:resteasy-client:3.7.0.Final"
compile "org.openjfx:javafx-graphics:11.0.2:win"
compile "org.openjfx:javafx-base:11.0.2:win"
compile "org.openjfx:javafx-controls:11.0.2:win"
compile "org.openjfx:javafx-fxml:11.0.2:win"
compile "org.openjfx:javafx-graphics:11.0.2:win"
}
Результат действительно странный java -jar ApplicationTest.jar возвращает
Ошибка: не удалось найти или загрузитьmain class de.test.MainFX Причина: java.lang.ClassNotFoundException: de.test.MainFX
Мы дважды проверили файл jar и файлы классов находятся в правильном месте (de \ test \Main.class и de \ test \ MainFX.class)
РЕДАКТИРОВАТЬ 2) FAT Jar со вторым основным классом
На основании этой статьи мы снова добавили плагин javafx,удалил зависимости javafx и определил Launcher-Main внутри манифеста и обычного FXMLПочта как mainClassName:
...
mainClassName = 'de.test.MainFX'
javafx {
version = 11
modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.web']
}
jar {
manifest {
attributes 'Main-Class': 'de.test.MainLauncher'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Результат
> Exception in thread "main" java.lang.NoClassDefFoundError:
> javafx/application/Application
> at java.base/java.lang.ClassLoader.defineClass1(Native Method)
> at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
> at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
> at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
> at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
> at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
> at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
> at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
> at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
> at de.test.MainLauncher.main(MainLauncher.java:7) Caused by: java.lang.ClassNotFoundException: javafx.application.Application
> at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
> at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
> at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
> ... 10 more
Итак, мы добавили зависимости для javafx снова со следующим результатом:
Graphics Device initialization failed for : d3d, sw
Error initializing QuantumRenderer: no suitable pipeline found
java.lang.RuntimeException: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
at com.sun.javafx.tk.quantum.QuantumRenderer.getInstance(QuantumRenderer.java:280)
at com.sun.javafx.tk.quantum.QuantumToolkit.init(QuantumToolkit.java:222)
at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:260)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.init(QuantumRenderer.java:94)
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:124)
... 1 more
Exception in thread "main" java.lang.RuntimeException: No toolkit found
at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:272)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.base/java.lang.Thread.run(Thread.java:834)