JavaFX 어플리케이션 배포
JavaFX 어플리케이션의 배포는 생각보다 단순하지 않을 수 있습니다. 특히 Java9+ 에서 JavaFX은 JDK로부터 분리되어 빌드방법도 조금 달라졌습니다. 이번 포스팅에서는 JavaFX 애플리케이션을 배포하는 방법을 알아보겠습니다.
포스팅에서의 최종 배포 형태는 다음과 같습니다.
- Desktop용 프로그램이 대상입니다.
- Windows용 exe 설치파일이 결과물입니다.
- 자체 JRE를 포함하여 추가적인 JRE 설치가 필요없습니다.
타 OS들은 기반 내용 설명과 References 들을 참고하여 충분히 응용할 수 있습니다.
커스텀 JRE를 생성하기 위해 jlink를 사용합니다.
jlink는 빌드과정에서 프로젝트에서 사용하는 모듈만 포함하는 커스텀 JRE를 만들어 포함시킵니다. 이를 통해 JRE의 용량을 획기적으로 줄일 수 있습니다. 마인크래프트 게임 또한 JLink를 사용하여 배포되고 있습니다.
JavaFX 외에도 Docker 이미지의 용량을 획기적으로 줄이는 등 다양한 활용이 가능합니다.
jlink를 사용하려면 프로젝트가 모듈형식이어야 합니다.
java9+ 부터 도입된 모듈시스템은 쉽게말해 패키지 단위의 접근제한설정입니다. 프로젝트에module-info.java
가 정의된 경우 따로 할 일은 없습니다. 해당 파일을 사용하지 않으려면 jdeps를 통해 종속성을 지정해주어야 합니다.
제작과정에서 조심할 부분은 사용할 모든 외부라이브러리가 모듈시스템을 지원해야합니다. 오래된 라이브러리나 리플렉션 관련 라이브러리를 사용하는 경우 조심하세요. 모듈 시스템이 업데이트되지 않아 jlink를 적용할 수 없게될 수 있습니다.
Non-Modular의 경우 빌드방법이 다릅니다.
비모듈식 라이브러리를 사용하는 경우 javafx-maven-plugin을 사용할 수 없습니다. Spring Boot와 같은 다수의 유명한 라이브러리들 또한 모듈시스템을 제공하지 않습니다. 이 경우, 먼저 Fat-jar을 생성하고, jdeps를 사용해 종속성을 추립니다. 이후 jlink를 이용하여 별도로 런타임을 생성하는 과정이 필요합니다. 시간이 된다면 추후 포스팅에서 다루겠습니다.
🤔 1. JavaFX 배포 고려사항
Java로 작성한 프로그램은 빌드 후 JAR
형태로 패키징 되곤 합니다. 이렇게 패키징된 JAR
는 java
혹은 javaw
커맨드를 통해 실행됩니다.
1
javaw -jar myapp.jar
하지만 JAR
파일은 최종적인 형태가 아닙니다.
- 기존 Java 개발자가 아니라면 실행을 위해 JRE를 설치해야 합니다.
- JRE가 설치되어 있더라도 하위버전이면 업그레이드 해야 합니다.
- 최종 사용자는 실행파일을 더블클릭해서 실행하기를 원합니다.
사용자를 늘리기 위해서는 유입에 장애물이 적어야 합니다. 이를 위해 JRE
를 배포에 포함시키고 exe
형태로 패키징하는 것이 합리적입니다.
📦 2. 배포파일에 JRE를 포함시키기
도입부에서 소개한 jlink
툴을 사용하면 사용자 정의 JRE가 포함된 배포가 가능합니다.
jlink
를 사용하기 위해서는 module-info.java
혹은 jdeps
를 통해 모듈을 정의해야 합니다. JavaFX는 해당 과정을 포함한 JavaFX-Maven-Plugin이 제공됩니다. 이번 포스팅에서는 플러그인 방식만 살펴보겠습니다. jdeps
와 커맨드를 사용하는 방법은 References를 참고하시길 바랍니다.
2-1. Maven Plugin을 사용하여 빌드
JavaFX-Maven-Plugin을 사용합니다. 문서가 잘 되있기에 상세한 설명은 생략합니다. 해당 플러그인을 사용하기 위해서는 module-info.java
가 정의되어있어야 합니다. 위치는 다음과 같습니다.
1
2
3
4
5
src/main/java
├── com.example.myModule
│ ├── Application.java
│ └── HelloFXController.java
└── module-info.java
module-info.java
의 간단한 사용법은 다음과 같습니다.
1
2
3
4
5
6
7
module com.example.myModule {
requires javafx.controls;
requires javafx.fxml;
opens com.example.myModule to javafx.fxml;
exports com.example.myModule;
}
개발과정에서 module is not visible
과 같은 메시지를 띄워주기 때문에, 그떄그때 requires
와 opens & exports
를 추가해가며 가시성을 조정하면 됩니다.
pom.xml
에 플러그인을 설정해주겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<mainClass>
com.example.myModule/com.example.myModule.HelloApplication
</mainClass>
<launcher>app</launcher>
<jlinkZipName>app</jlinkZipName>
<jlinkImageName>app</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
</configuration>
</execution>
</executions>
</plugin>
IntelliJ 기본 생성 파일입니다. 각 인자들은 런타임 이미지를 만드는데 사용됩니다. 간략히 설명하겠습니다.
- mainClass : 모듈명/메인클래스명
- launcher : 생성될 런처파일 이름
- jlinkZipName : 생성될 zip 파일 이름
- jlinkImageName : 생성될 이미지폴더 이름
- noManPages : Java Docs 관련 내용 제외
- stripDebug : 디버그정보 제외
- noHeaderFiles : 링크에 의하면, C 네이티브 코딩 관련 라이브리리 폴더입니다. 사용하지 않는다면 필요없습니다.
추가적인 설정들은 JavaFX-Maven-Plugin에 정리되어있습니다. 하지만 위 설정만 사용해도 충분합니다.
이제 Maven의 javafx:jlink
생명주기를 실행하여 jlink할 수 있습니다.
1
.\mvnw clean javafx:jlink
추가적으로 이 플러그인이 있으면 javafx:run
생명주기 또한 제공되어, 빌드 후 바로 실행해볼 수 있습니다.
1
.\mvnw clean javafx:run
2-2. jlink 결과물 확인
먼저 빌드(jlink)의 최종결과를 살펴보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
target
├── app
│ ├── bin
│ │ ├── app
│ │ ├── app.bat
│ │ ├── ...
│ ├── conf
│ ├── legal
│ ├── lib
│ └── release
├── classes
│ ├── ...
├── generated-sources
│ ├── ...
├── maven-status
│ ├── ...
└── app.zip
최종 결과물은 app.zip
입니다. 결과물 폴더들을 압축한 파일입니다. 26.6MB
네요. 결과물 내용을 보면 앞서 플러그인에서 설정한 인자들이 어떤 것과 매칭되는지 쉽게 알 수 있습니다.
app/bin
폴더 내에 app
과 Window용 app.bat
파일이 포함된 것을 볼 수 있습니다. 두 파일이 launcher 이며, 클릭해서 실행 가능합니다. 파일 내용은 동일합니다.
1
2
3
4
#!/bin/sh
JLINK_VM_OPTIONS=
DIR=`dirname $0`
$DIR/java $JLINK_VM_OPTIONS -m com.example.javafxdeployexample/com.example.javafxdeployexample.HelloApplication "$@"
단순한 bash shell 이네요. 좋습니다.
app/bin
폴더를 추가적으로 둘러보면 JRE가 포함된 것을 알 수 있습니다. 즉, 현재 상태로도 배포가 가능하긴 합니다. 최종사용자는 압축을 풀고 app
혹은 app.bat
을 실행하면 됩니다.
💻 3. 실행파일 형태로 패키징
드디어 마지막입니다. 플랫폼에 종속적인 실행파일로 최종 패키징합시다. 여기에는 jpackage
툴이 사용되는데, 역시 JPackage-Maven-Plugin이 제공됩니다. 역시 커맨드 사용법은 생략하고 플러그인 사용법만 소개하겠습니다.
먼저 플러그인 설정에 앞서 Wix를 설치해주겠습니다. Windows용 Installer를 구성하는데 필요한 프로그램이며, jpackage
내부에서 사용합니다. 링크에서 wix311.exe
를 다운받아 설치해주세요.
pom.xml
에 플러그인을 설정해주겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<plugin>
<groupId>org.panteleyev</groupId>
<artifactId>jpackage-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<name>HelloFX</name>
<appVersion>0.0.1</appVersion>
<vendor>com.example</vendor>
<destination>target/dist</destination>
<module>com.example.javafxdeployexample/com.example.javafxdeployexample.HelloApplication</module>
<runtimeImage>target/app</runtimeImage>
<winShortcut>true</winShortcut>
<icon>appIcon.ico</icon>
<javaOptions>
<option>-Dfile.encoding=UTF-8</option>
</javaOptions>
</configuration>
</plugin>
- name, appVersion, vendor : 프로그램에 대한 설명을 지정합니다.
- destination : 결과물이 저장될 폴더를 지정합니다.
- module : 모듈/메인클래스명
- runtimeImage : 위 jlink단계에서 생성된 이미지폴더를 지정합니다.
바로가기 생성, 아이콘 설정 등 다양한 옵션을 사용할 수 있으며, VM 옵션, Parameter 등도 추가할 수 있습니다. 자세한 내용은 JPackage 가이드를 참고하세요.
설정값이 OS 종속적인 것을 알 수 있습니다. 여러 OS용 설정값을 분리해서 작성할 수 있습니다. 플러그인 가이드를 참고하세요. Examples 섹션을 보면 됩니다.
jpackage 가이드는 커맨드를 기준으로 설명하고 있습니다.
플러그인에서는 자동완성이 제공됩니다. 예를들어 windows 관련 설정은<win
까지만 입력 후Ctrl + Space
로 확인할 수 있습니다.linux
나mac
도 동일하게 사용할 수 있습니다. 인자가 없다면 단순 flag 이므로 true로 설정하면 됩니다.
VM옵션을 사용하여 최종적인 튜닝을 고려하세요.
조금 맥락에서 벗어난 이야기지만 VM 옵션을 사용하여 최종적인 튜닝을 할 수 있습니다. 힙사이즈, GC전략, 네이티브 메서드 캐싱 관련하여 고려해볼 수 있습니다. 추후 포스팅에서 다루겠습니다.
최종 결과를 보겠습니다. Window Installer 형태로 배포가 되었으며 용량은 28MB
입니다. 적절하네요. 아이콘도 제대로 뜨고 바탕화면 바로가기도 제대로 생성됩니다.
위치 옵션을 주지 않으면 C:\Program Files
폴더에 설치됩니다. 프로그램 추가/제거
에서 삭제할 수 있습니다. 실행이 잘 되는군요. 이로써 모든 배포과정이 마무리되었습니다.
📝 4. 정리
우리는 JavaFX 빌드를 하기 위해서 다음과 같은 과정을 거쳤습니다.
module-info.java
혹은jdeps
툴을 이용해서 종속성 묶어주기jlink
툴을 사용하여 런타임 이미지 생성jpackage
툴을 사용하여 Installer로 패키징
플러그인 설정이 완료되었다면
1
.\mvnw clean javafx:jlink jpackage:jpackage
커맨드를 통해 target/dist
폴더에서 결과물을 얻을 수 있습니다.
더 나아가 javafx:jlink
와 jpackage:jpackage
두 Maven 생명주기를 사용자 정의로 묶을 수 있습니다. 하지만 생략하겠습니다.
최종예제는 Github에서 확인할 수 있습니다. 모두 고생하셨습니다.
References
- https://dev.to/cherrychain/javafx-jlink-and-jpackage-h9
- https://learn.microsoft.com/ko-kr/java/openjdk/java-jlink-runtimes
- https://docs.oracle.com/en/java/javase/18/jpackage/packaging-overview.html#GUID-C1027043-587D-418D-8188-EF8F44A4C06A
- https://docs.oracle.com/en/java/javase/15/docs/specs/man/jpackage.html