前々から気になっていたWildFly Swarmですが、そろそろちょっと試してみることにしました。
Rightsize your Java EE Applications | Thorntail
WildFly Swarmって?
WildFlyを組み込んで、実行可能JARを作成したり、必要な機能やその他のライブラリなどを統合した機能を提供してくれる仕組み。
詳しくは、こちらの資料などを見ていただくとよいかと。
WildFly Swarm - Rightsize Your Java EE Apps from Yoshimasa Tanabe
また、ドキュメントはこちら。
こちらも、合わせて読むとよいでしょう。
サンプルリポジトリもあるので、適宜参考に。
https://github.com/wildfly-swarm/wildfly-swarm-examples
自分がWildFly Swarmを使うモチベーション
アプリケーションサーバーにWARファイルをデプロイするのが面倒。
これに尽きます。
なので、uberjarとかMicroserviesとかの高尚な(?)話は割とよくて、
ふつうに作ったJava EEアプリケーションを簡単に起動したい、というのがまずは使ってみたい動機です。
またMicroProfile Serverという、JARファイルにWARファイルを与えて起動できるというものも登場したので(Payara Microっぽいですね)、こちらを使いたいなぁと。
WildFly Swarmのuberjar、でかいんですよ…。
なので、作るWARファイルはまずはふつうのJava EEの範囲でのものを考えます。WildFly Swarmならではのものは、そのうち気が向いたら試していくかも?
使ってみる
というわけで、この目的でWildFly Swarmを使ってアプリケーションを動かしてみます。今回は、JAX-RSとCDIで簡単なアプリケーションを書いて、最後にArquillianでテストまでしてみます。
<?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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings</groupId> <artifactId>wildfly-swarm-mp-jaxrs-cdi</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> <wildfly.swarm.version>2016.9</wildfly.swarm.version> <arquillian.version>1.1.10.Final</arquillian.version> <scala.major.version>2.11</scala.major.version> <scala.version>${scala.major.version}.8</scala.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>bom-all</artifactId> <version>${wildfly.swarm.version}</version> <scope>import</scope> <type>pom</type> </dependency> <dependency> <groupId>org.jboss.arquillian</groupId> <artifactId>arquillian-bom</artifactId> <version>${arquillian.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>3.0.19.Final</version> <scope>test</scope> </dependency> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>jaxrs-cdi</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>arquillian</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.junit</groupId> <artifactId>arquillian-junit-container</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.scalatest</groupId> <artifactId>scalatest_${scala.major.version}</artifactId> <version>3.0.0</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>app</finalName> <plugins> <plugin> <groupId>org.wildfly.swarm</groupId> <artifactId>wildfly-swarm-plugin</artifactId> <version>${wildfly.swarm.version}</version> <!-- <executions> <execution> <goals> <goal>package</goal> </goals> </execution> </executions> --> </plugin> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>3.2.2</version> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion>${scala.version}</scalaVersion> <args> <arg>-Xlint</arg> <arg>-unchecked</arg> <arg>-deprecation</arg> <arg>-feature</arg> </args> <recompileMode>incremental</recompileMode> </configuration> </plugin> </plugins> </build> </project>
Scalaが入っているのは、放っておいてください(笑)。
BOMとして、WildFly SwarmのものとArquillianのものを入れておきます。
providedスコープにJava EEのAPI(とScala)を追加。あとは、テスト用のものです。WildFly Swarmのjaxrs-cdiがtestスコープに混じっていますが、これはテストの起動時には要るから…。
また、JAX-RSのリソースクラスに対して、HTTP経由でテストするため、RESTEasyのClient APIも入れています。
WildFly SwarmのMaven Pluginですが、uberjarは作らないのでexecutionsのところはコメントアウト。
<plugin> <groupId>org.wildfly.swarm</groupId> <artifactId>wildfly-swarm-plugin</artifactId> <version>${wildfly.swarm.version}</version> <!-- <executions> <execution> <goals> <goal>package</goal> </goals> </execution> </executions> --> </plugin>
ビルドしてできあがるWARファイルの名前は、「app.war」とします。
<finalName>app</finalName>
サンプルアプリケーション
足し算をするだけの、簡単なアプリケーションを作ります。
JAX-RSの有効化。
src/main/scala/org/littlewings/javaee7/microprofile/rest/JaxrsActivator.scala
package org.littlewings.javaee7.microprofile.rest import javax.ws.rs.ApplicationPath import javax.ws.rs.core.Application @ApplicationPath("rest") class JaxrsActivator extends Application
JAX-RSリソースクラス。
src/main/scala/org/littlewings/javaee7/microprofile/rest/CalcResource.scala
package org.littlewings.javaee7.microprofile.rest import javax.enterprise.context.RequestScoped import javax.inject.Inject import javax.ws.rs.core.MediaType import javax.ws.rs.{GET, Path, Produces, QueryParam} import org.littlewings.javaee7.microprofile.service.CalcService @Path("calc") @RequestScoped class CalcResource { @Inject var calcService: CalcService = _ @Path("add") @GET @Produces(Array(MediaType.TEXT_PLAIN)) def add(@QueryParam("a") a: Int, @QueryParam("b") b: Int): Int = calcService.add(a, b) }
CDI管理Bean。
src/main/scala/org/littlewings/javaee7/microprofile/service/CalcService.scala
package org.littlewings.javaee7.microprofile.service import javax.enterprise.context.ApplicationScoped @ApplicationScoped class CalcService { def add(a: Int, b: Int): Int = a + b }
とまあ、こんな感じで。特にWildFly Swarmに依存するコードは入っていません。
実行
いくつか方法がありますが、まずはWildFly SwarmのMaven Pluginで起動してみます。
$ mvn wildfly-swarm:run
確認。
$ curl 'http://localhost:8080/rest/calc/add?a=5&b=3' 8
OKですね。
次に、MicroProfile Serverで起動してみます。ダウンロードページから、「microprofile-hollowswarm.jar」をダウンロードします。
http://wildfly-swarm.io/downloads/
現時点では、「microprofile-2016.9-hollowswarm.jar」というものになります。
WARファイルを作ります。
$ mvn package
WARファイルを指定して、「java -jar」で起動。
$ java -jar microprofile-2016.9-hollowswarm.jar target/app.war
確認。この場合は、URLにコンテキストパス(今回は「app」)が入ります。
$ curl 'http://localhost:8080/app/rest/calc/add?a=5&b=3' 8
uberjarで実行したい場合は、WildFly SwarmのMaven Pluginをpackage時に実行するように仕込んでください。
<plugin> <groupId>org.wildfly.swarm</groupId> <artifactId>wildfly-swarm-plugin</artifactId> <version>${wildfly.swarm.version}</version> <executions> <execution> <goals> <goal>package</goal> </goals> </execution> </executions> </plugin>
これで、「mvn package」でuberjarができあがります。
テストをする
最後に、Arquillianを使ってテストをします。
特にArquillianの設定ファイルなどを用意しなくても、実行可能です。
Arquillian を用いた Integration Test
それでは、テストを用意します。
JAX-RS側のテストコード。こちらは、@RunAsClientでクライアントとして動かします。
src/test/scala/org/littlewings/javaee7/microprofile/rest/CalcResourceTest.scala
package org.littlewings.javaee7.microprofile.rest import java.net.URL import javax.ws.rs.ApplicationPath import javax.ws.rs.client.ClientBuilder import org.jboss.arquillian.container.test.api.{Deployment, RunAsClient} import org.jboss.arquillian.junit.Arquillian import org.jboss.arquillian.test.api.ArquillianResource import org.jboss.shrinkwrap.api.ShrinkWrap import org.jboss.shrinkwrap.resolver.api.maven.Maven import org.junit.Test import org.junit.runner.RunWith import org.littlewings.javaee7.microprofile.service.CalcService import org.scalatest.Matchers import org.wildfly.swarm.Swarm import org.wildfly.swarm.arquillian.CreateSwarm import org.wildfly.swarm.jaxrs.JAXRSArchive object CalcResourceTest { @Deployment def createDeployment: JAXRSArchive = ShrinkWrap .create(classOf[JAXRSArchive]) .addClasses(classOf[JaxrsActivator], classOf[CalcResource], classOf[CalcService]) .addAsLibraries( Maven .resolver .loadPomFromFile("pom.xml") .importRuntimeDependencies .resolve("org.scalatest:scalatest_2.11:3.0.0") .withTransitivity .asFile: _* ) @CreateSwarm def newContainer: Swarm = new Swarm } @RunWith(classOf[Arquillian]) @RunAsClient class CalcResourceTest extends Matchers { private val resourcePrefix: String = classOf[JaxrsActivator] .getAnnotation(classOf[ApplicationPath]) .value @ArquillianResource private var deploymentUrl: URL = _ @Test def calcTest(): Unit = { val client = ClientBuilder.newBuilder.build try { val response = client .target(s"${deploymentUrl}${resourcePrefix}/calc/add?a=5&b=3") .request .get response.readEntity(classOf[String]) should be("8") response.close() } finally { client.close() } } }
CDI管理Beanのテスト。
src/test/scala/org/littlewings/javaee7/microprofile/service/CalcServiceTest.scala
package org.littlewings.javaee7.microprofile.service import javax.inject.Inject import org.jboss.arquillian.container.test.api.Deployment import org.jboss.arquillian.junit.Arquillian import org.jboss.shrinkwrap.api.ShrinkWrap import org.jboss.shrinkwrap.api.spec.WebArchive import org.jboss.shrinkwrap.resolver.api.maven.Maven import org.junit.Test import org.junit.runner.RunWith import org.scalatest.Matchers import org.wildfly.swarm.Swarm import org.wildfly.swarm.arquillian.CreateSwarm object CalcServiceTest { @Deployment def createDeployment: WebArchive = ShrinkWrap .create(classOf[WebArchive]) .addClass(classOf[CalcService]) .addAsLibraries( Maven .resolver .loadPomFromFile("pom.xml") .importRuntimeDependencies .resolve("org.scalatest:scalatest_2.11:3.0.0") .withTransitivity .asFile: _* ) @CreateSwarm def newContainer: Swarm = new Swarm } @RunWith(classOf[Arquillian]) class CalcServiceTest extends Matchers { @Inject var calcService: CalcService = _ @Test def addTest(): Unit = calcService.add(5, 3) should be(8) }
コードの書き方自体は通常のArquillianを使った場合と変わりませんが、WildFly Swarmのコンテナについての設定などを入れる場合は、@CreateSwarmを付与したstaticメソッドでコンテナを返すように実装するようです。
@CreateSwarm def newContainer: Swarm = new Swarm
今回は特になにもしていないので、このメソッドは実はなくても動作します。
あとは、テストを実行するだけ。
$ mvn test
ちょっと重いのですが、これでArquillianを使ったテストが実行されます。
まとめ
WildFly Swarmを使って、ふつうのJava EEアプリケーションを動作させてみました。
このままだとIDEからmainメソッドで実行できないじゃないかとかありますが、一応やりたいことは実現できましたと。
IDEからmainメソッド欲しかったらさすがにコンテナ使ったコード書くかなぁとか、DataSource使いたかったらどうしようとかあるのですが…。
DataSourceとかは、どちらかというと設定ファイルにしたい派なんですよね。アプリケーションサーバー依存になりますけど、*-ds.xmlとか用意すればいいのかな。
あと、Arquillianを使ったテストはやっぱり重いので、UnitTestの時はApache DeltaSpikeでできる範囲は代用とかかなぁとちょっと思ったり。
まあ、個人の趣味程度に遊んでいきたいと思います。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/wildfly-swarm-mp-jaxrs-cdi