- はじめに
- プロジェクトの作成
- Payara を組み込みモードで動かす
- JUL(Java Util Logger) から SLF4J への変更
- buildSrc の追加
- war モジュールの作成
- Hello サーブレット
- アプリケーションのデプロイ
- Hello JAX-RS
- まとめ
はじめに
いまさら何故? という気はしますが、JavaEE 8 の環境を (Payara Micro ではなく) Payara 5 の embedded モードで動かす手順です。
プロジェクトの作成
Gradle 6.7 を使います。
プロジェクトディレクトリを作成して gradle init
タスクでプロジェクトを作成します。
$ mkdir javaee8-starter $ cd javaee8-starter $ gradle init
gradle init
のオプションは以下のように選択します。
Select type of project to generate: 2: application Enter selection (default: basic) [1..4] 2 Select implementation language: 3: Java Enter selection (default: Java) [1..6] 3 Split functionality across multiple subprojects?: 1: no - only one application project Enter selection (default: no - only one application project) [1..2] 1 Select build script DSL: 2: Kotlin Enter selection (default: Groovy) [1..2] 2 Select test framework: 4: JUnit Jupiter Enter selection (default: JUnit 4) [1..4] 4 Project name (default: javaee8-starter): Source package (default: javaee8.starter):
ビルドスクリプトには Gradle Kotlin DSL を使うこととしました。
Payara を組み込みモードで動かす
作成されたプロジェクトの app/build.gradle.kts
を以下のように編集します。
plugins { application } repositories { jcenter() } dependencies { implementation("fish.payara.extras:payara-embedded-all:5.2020.7") testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") } application { mainClass.set("javaee8.starter.App") } tasks.test { useJUnitPlatform() }
fish.payara.extras:payara-embedded-all:5.2020.7
を依存に追加しました。
App.java
を編集して Payara 5 を embedded で起動します。
package javaee8.starter; import org.glassfish.embeddable.BootstrapProperties; import org.glassfish.embeddable.GlassFish; import org.glassfish.embeddable.GlassFishProperties; import org.glassfish.embeddable.GlassFishRuntime; public class App { public static void main(String[] args) throws Exception { BootstrapProperties bootstrap = new BootstrapProperties(); GlassFishRuntime runtime = GlassFishRuntime.bootstrap(bootstrap); GlassFishProperties properties = new GlassFishProperties(); properties.setPort("http-listener", 8083); properties.setPort("https-listener", 8184); GlassFish glassfish = runtime.newGlassFish(properties); glassfish.start(); } }
GlassFishRuntime
から newGlassFish()
することで GlassFish のインスタンスが得られます。
glassfish.start()
にてサーバが起動できます。以下で実行してみましょう。
$ gradlew run
アプリケーションのデプロイは行っていないため、404 になりますが、サーバは起動できました。
後ほど行いますが、アプリケーションのデプロイは以下のようにすることができます。
Path war = Paths.get("path/to/app.war"); Deployer deployer = glassfish.getDeployer(); deployer.deploy(war.toFile(), "--name", "app", "--contextroot", "app", "--force", "true");
JUL(Java Util Logger) から SLF4J への変更
先程起動したサーバは JULによりログ出力されて見にくいため、SLF4J に変更しておきましょう。
JULのプロパティファイルを作成してシステムプロパティで設定します。
app/src/main/resources/logging.properties
に以下のようなプロパティファイルを作成します。
handlers=org.slf4j.bridge.SLF4JBridgeHandler
ロガーのハンドラとして org.slf4j.bridge.SLF4JBridgeHandler
を指定する設定となります。
このブリッジを使うには app/build.gradle.kts
に org.slf4j:jul-to-slf4j
の依存を追加します。
org.slf4j:slf4j-api
と ch.qos.logback:logback-classic
とともに追加しましょう。
dependencies { // ... implementation("org.slf4j:slf4j-api:1.7.30") implementation("ch.qos.logback:logback-classic:1.2.3") implementation("org.slf4j:jul-to-slf4j:1.7.30") }
App.java
のメインメソッドの先頭に以下を追加することで、 SLF4J によるログ出力が行われます。
System.setProperty("java.util.logging.config.file", App.class.getResource("/logging.properties").getPath());
ログの設定は、logback の設定ファイルを app/src/main/resources/logback.xml
に作成することで行うことができます。
例えば以下のようになります。
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration>
buildSrc の追加
先程作成したサーバにデプロイするアプリケーションを作成するために、プロジェクトをマルチモジュールプロジェクトに変えます。
これに伴い、ビルドスクリプトを Gradle のベストプラクティスに従い buildSrc を利用したものに変更していきます。
buildSrc
ディレクトリを以下のように作成し、 build.gradle.kts
を作成します。
$ mkdir -p buildSrc/src/main/kotlin $ touch buildSrc/build.gradle.kts
作成した buildSrc/build.gradle.kts
は以下のように編集します。
plugins { `kotlin-dsl` } repositories { gradlePluginPortal() }
この辺はお決まりのパターンです。詳細は以下を参照してください。
conventions.gradle.kts
を作成していきます。
今回は以下の3つを作成することにします。
$ touch buildSrc/src/main/kotlin/javaee8.starter.java-application-conventions.gradle.kts $ touch buildSrc/src/main/kotlin/javaee8.starter.java-common-conventions.gradle.kts $ touch buildSrc/src/main/kotlin/javaee8.starter.java-war-conventions.gradle.kts
最後の javaee8.starter.java-war-conventions.gradle.kts
は、この後で作成する war モジュール用のスクリプトになります。
それぞれを以下のように編集します。
javaee8.starter.java-application-conventions.gradle.kts
plugins { id("javaee8.starter.java-common-conventions") application } application { applicationDefaultJvmArgs = listOf("--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED") }
デプロイ時のリフレクションでエラーとなるため、実行時のコマンドライン引数に --add-opens
を指定しています。
javaee8.starter.java-common-conventions.gradle.kts
plugins { java } repositories { jcenter() } dependencies { implementation("org.slf4j:slf4j-api:1.7.30") implementation("ch.qos.logback:logback-classic:1.2.3") implementation("org.slf4j:jul-to-slf4j:1.7.30") testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") } tasks.test { useJUnitPlatform() } java { toolchain { languageVersion.set(JavaLanguageVersion.of(11)) } }
Payara 5 は Java 11 サポートとなっているため、toolchain
で定義しました。
Toolchain については以下を参照してください。
javaee8.starter.java-war-conventions.gradle.kts
plugins { id("javaee8.starter.java-common-conventions") war } dependencies { annotationProcessor("org.hibernate:hibernate-jpamodelgen:5.4.27.Final") providedCompile("fish.payara.extras:payara-embedded-all:5.2020.7") }
後ほど利用するため JPA のアノテーションプロセッサを追加しておきました(eclipseLink のものは使いにくいので hibernate-jpamodelgen
を使っています)。
作成した conventions に合わせて app/build.gradle.kts
を以下のように変更します。
plugins { id("javaee8.starter.java-application-conventions") } dependencies { implementation("fish.payara.extras:payara-embedded-all:5.2020.7") runtimeOnly(project(":war", "archives")) } application { mainClass.set("javaee8.starter.App") }
依存には、これから作成する war モジュールを runtimeOnly
として追加しています。
これで buildSrc
の準備は完了しましたので、war を作成するモジュールを準備しましょう。
war モジュールの作成
war モジュール用のディレクトリを作成します。
$ mkdir -p war/src/main/java/javaee8/starter $ mkdir -p war/src/main/resources/META-INF
build.gradle.kts
も作成しましょう。
$ touch war/build.gradle.kts
war/build.gradle.kts
を以下のように編集します。
plugins {
id("javaee8.starter.java-war-conventions")
}
追加したモジュールは settings.gradle.kts
にインクルード設定します。
rootProject.name = "javaee8-starter" include("app", "war")
これで war 用のモジュールが作成できました。
ここまでで、ディレクトリ構成は以下のようになります。
Hello サーブレット
簡単なサーブレットを作成します。
$ touch war/src/main/java/javaee8/starter/HelloServlet.java
以下のように編集します。
package javaee8.starter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet(name = "hello", urlPatterns = { "/hello" }) public class HelloServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.println("<html>"); writer.println("<html><head><title>HelloServlet</title></head>"); writer.println("<body><h1>Hello Servlet</h1></body>"); writer.println("</html>"); } }
@WebServlet
でアノテートすることで、web.xml
などの設定ファイルは不要です。
では実行してみましょう。とその前にデプロイする処理を書きます。
アプリケーションのデプロイ
App.java
を編集してデプロイ処理を追加します。
glassfish.start();
以下に追加しました。
public class App { public static void main(String[] args) throws Exception { System.setProperty("java.util.logging.config.file", App.class.getResource("/logging.properties").getPath()); BootstrapProperties bootstrap = new BootstrapProperties(); GlassFishRuntime runtime = GlassFishRuntime.bootstrap(bootstrap); GlassFishProperties properties = new GlassFishProperties(); properties.setPort("http-listener", 8083); properties.setPort("https-listener", 8184); GlassFish glassfish = runtime.newGlassFish(properties); glassfish.start(); File war = Arrays.stream(System.getProperty("java.class.path").split(":")) .filter(s -> s.endsWith(".war")) .map(File::new) .findFirst() .orElseThrow(); Deployer deployer = glassfish.getDeployer(); deployer.deploy( war, "--name", "app", "--contextroot", "app", "--force", "true"); } }
システムプロパティからクラスパスを取得し、その中に含まれる war ファイルを見つけてデプロイ処理しています。
今回は、Java9以降のモジュールシステムを利用していないため、単にクラスパスから取得したものを利用していますが、モジュールシステムを利用する場合には ModuleLayer
などから取得する必要があると思います(がここでは触れません)。
実行しましょう。
$ gradlew run
http://localhost:8083/app/hello
にアクセスすれば以下のようにサーブレットの実行結果が得られます。
Hello JAX-RS
ついでに JAX-RS も入れておきましょう。
JAX-RS を有効化するため、Application
を継承したクラスを作成します。
package javaee8.starter; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("rs") public class JaxRsActivator extends Application { }
リソースとして以下を作成します。
package javaee8.starter; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("hello") public class HelloResource { @GET @Produces(MediaType.APPLICATION_JSON) public Greeting get() { return new Greeting("Hello", "Thom"); } public static class Greeting { private String message; private String name; public Greeting(String message, String name) { this.message = message; this.name = name; } public String getMessage() { return message; } public String getName() { return name; } } }
JavaEE 8 からは JSON-B が入っているので、何も考えずに、オブジェクトをリターンすれば以下のような JSON が得られます。
まとめ
Payara 5 を Gradle のマルチモジュールプロジェクトで embedded モードとして動かす方法について見てきました。
ちなみに、つい先日(2020年11月22日) Jakarta EE 9 がリリースとなりましたが、javax
-> jakarta
への名前空間の変更(Eclipse Transformerというツールで変換可能)と、古いAPIのプルーニングが主なので、あまり大きなインパクトはありません。