以前書いた、こちらのエントリ
組み込みTomcatでJAX-RS(RESTEasy)とCDIを使う
http://d.hatena.ne.jp/Kazuhira/20150308/1425780313
の、コンテナを組み込みTomcatからUndertowに変えて遊んでみたというお話。
実装する時の条件は、前と変わらず
といった感じです。
前に組み込みTomcatを使った時はけっこう簡単にいったのですが、Undertowに変えるとなかなかてこずりました…。
では、結果を書いていきましょう。
Mavenの定義
依存関係もろもろ。pomは、このような形で用意しました。
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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings</groupId> <artifactId>embedded-undertow-jaxrs-cdi</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>io.undertow</groupId> <artifactId>undertow-core</artifactId> <version>1.1.3.Final</version> </dependency> <dependency> <groupId>io.undertow</groupId> <artifactId>undertow-servlet</artifactId> <version>1.1.3.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-servlet-initializer</artifactId> <version>3.0.11.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-cdi</artifactId> <version>3.0.11.Final</version> </dependency> <dependency> <groupId>org.jboss.weld.servlet</groupId> <artifactId>weld-servlet</artifactId> <version>2.2.9.Final</version> </dependency> <dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>2.2.5</version> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.el</artifactId> <version>2.2.6</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.servlet.jsp</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>2.11.6</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.2.3.RELEASE</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>3.2.0</version> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion>2.11.6</scalaVersion> <args> <arg>-Xlint</arg> <arg>-unchecked</arg> <arg>-deprecation</arg> <arg>-feature</arg> </args> <recompileMode>incremental</recompileMode> </configuration> </plugin> </plugins> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project>
基本的に、組み込みTomcatをUndertowに変えた感じなのですが、weld-servletを使う時にELとJSPを要求するようなので、仕方なく依存関係に追加しました…。
今回は、resteasy-undertowはスルー。
あと、Spring Bootのプラグインがいますが、Fat JAR化のためだけにこれがいます。
JAX-RSリソースとCDI管理Bean
前回と完全に同じソースコードです。小さいので、まとめて全部載せます。
// JAX-RS有効化 package org.littlewings.javaee7.rest import javax.ws.rs.ApplicationPath import javax.ws.rs.core.Application @ApplicationPath("rest") class JaxrsApplication extends Application ////////////////////////////////////////////// // JAX-RSリソース package org.littlewings.javaee7.rest import javax.inject.Inject import javax.ws.rs.{ GET, Path, Produces, QueryParam } import javax.ws.rs.core.MediaType import org.littlewings.javaee7.service.CalcService @Path("calc") class CalcResource { @Inject private var calcService: CalcService = _ @GET @Path("add") @Produces(Array(MediaType.TEXT_PLAIN)) def add(@QueryParam("a") a: Int, @QueryParam("b") b: Int): Int = calcService.add(a, b) } ////////////////////////////////////////////// // CDI管理Bean package org.littlewings.javaee7.service import javax.enterprise.context.RequestScoped @RequestScoped class CalcService { def add(a: Int, b: Int) = a + b }
単純に、クエリパラメータを足し算するだけのクラス群です。
Undertowの起動とJAX-RS/CDI統合
ここが、Tomcatとは大きく変わりました。
結果のクラスをまず載せると、このような形に。
src/main/scala/org/littlewings/javaee7/UndertowBootstrap.scala
package org.littlewings.javaee7 import scala.collection.JavaConverters._ import scala.io.StdIn import io.undertow.{ Handlers, Undertow } import io.undertow.server.HttpHandler import io.undertow.server.handlers.PathHandler import io.undertow.servlet.Servlets import io.undertow.servlet.api.ServletContainerInitializerInfo import io.undertow.servlet.util.DefaultClassIntrospector import org.jboss.resteasy.plugins.servlet.ResteasyServletInitializer import org.jboss.weld.environment.servlet.EnhancedListener import org.littlewings.javaee7.rest._ object UndertowBootstrap { def main(args: Array[String]): Unit = { val contextPath = "" val port = 8080 val deployment = Servlets .deployment .setClassLoader(getClass.getClassLoader) .setContextPath(contextPath) .setDeploymentName("jax-rs-with-cdi") // .addInitParameter("org.jboss.weld.environment.servlet.archive.isolation", "false") // なくても動くみたい .addInitParameter("resteasy.injector.factory", "org.jboss.resteasy.cdi.CdiInjectorFactory") .addServletContainerInitalizer(new ServletContainerInitializerInfo(classOf[EnhancedListener], DefaultClassIntrospector.INSTANCE.createInstanceFactory(classOf[EnhancedListener]), null)) .addServletContainerInitalizer(new ServletContainerInitializerInfo(classOf[ResteasyServletInitializer], DefaultClassIntrospector.INSTANCE.createInstanceFactory(classOf[ResteasyServletInitializer]), jaxrsClasses)) val manager = Servlets.defaultContainer.addDeployment(deployment) manager.deploy() val serverHandler = manager.start() val handler: HttpHandler = if (contextPath.isEmpty) serverHandler else Handlers.path.addPrefixPath(contextPath, serverHandler) val server = Undertow .builder .addHttpListener(port, "localhost") .setHandler(handler) .build try { // Undertowの起動 server.start() // Enter打ったら終了 StdIn.readLine("> Enter stop") } finally { // Undertowの停止 server.stop() } } private def jaxrsClasses: java.util.Set[Class[_]] = Set[Class[_]]( classOf[JaxrsApplication], classOf[CalcResource] ).asJava }
Tomcatの時との違いを、ちょっとずつ書いていきます。
Weldの以下の設定は、今回はなくても動いてくれました。ちょっと不思議でしたが、どういう扱いなのだろう…。
// .addInitParameter("org.jboss.weld.environment.servlet.archive.isolation", "false") // なくても動くみたい
RESTEasyとCDIのつなぎ込みは、Tomcatと一緒です。
.addInitParameter("resteasy.injector.factory", "org.jboss.resteasy.cdi.CdiInjectorFactory")
で、WeldのServletContainerInitializerを、どうも自分で設定する必要があるっぽいです。これが追加されることで、BeanManagerが使えるようになります。
.addServletContainerInitalizer(new ServletContainerInitializerInfo(classOf[EnhancedListener], DefaultClassIntrospector.INSTANCE.createInstanceFactory(classOf[EnhancedListener]), null))
BeanManagerが使用できないと、RESTEasyのCdiInjectorFactoryが初期化に失敗します。
Tomcatだと、ServiceLoaderの仕組みで頑張ってくれていたのだろうと思っていたのですが、今回ここでハマったのでTomcat実装を見てみたらServiceLoaderっぽいことを自前で実装していました…。
WeldのServletContainerInitializerが効かないということは、RESTEasyのServletContainerInitializerもダメなので、自分で登録。
.addServletContainerInitalizer(new ServletContainerInitializerInfo(classOf[ResteasyServletInitializer],
DefaultClassIntrospector.INSTANCE.createInstanceFactory(classOf[ResteasyServletInitializer]),
jaxrsClasses))
UndertowのServletContainerInitializerInfoのコンストラクタの第3引数は、ServletContainerInitializer#onStartupに渡される引数そのものになります。
で、本来ここでJAX-RSのリソースクラスを登録してくれるはずなのですが、Undertowの場合スキャンしてくれる人がいないので、ここも自分で設定することに…。
private def jaxrsClasses: java.util.Set[Class[_]] = Set[Class[_]]( classOf[JaxrsApplication], classOf[CalcResource] ).asJava
なお、今回はResteasyServletInitializerを使用しましたが、後述のHammockもそうですがRESTEasyのServletを使う方法でもかまいません。TomcatでもServletは使わなかったので、ServletContainerInitializerで合わせただけの好みの話です。ただ、Servletを選んでもJAX-RSのリソースクラスはやっぱり手動で登録する必要があります。
動作確認的な
ここまで作ったら、パッケージングして
$ mvn package
起動。
java -jar target/embedded-undertow-jaxrs-cdi-0.0.1-SNAPSHOT.jar
確認。
$ curl "http://localhost:8080/rest/calc/add?a=5&b=8" 13
OKですね!
浮いているUndertowサーバは、Enterで終了します。
参考にしたもの、感想などなど
今回のソースを書くにあたって、@emaggameさんから教えていただいたHammockというものをかなり参考にしました。
@kazuhira_r ぜひ hammock を引き継いであげてください! 4ヶ月前にライブラリのアップデートだけひっそりやっているので、完全にやる気がないわけじゃないかもですが。。URL
2015-04-01 00:07:51 via TweetDeck to @kazuhira_r
Hammock, a lightweight integration of RestEasy, Undertow, and Weld.
https://github.com/johnament/hammock
前回Tomcatでやった時は、BeanManagerをJNDIリソースに登録してルックアップ可能な形で実装したのですが、今回Undertowだとそのあたりが厳しくて、やめようかなぁと思ったところに教えていただいたのがこちらでした。
最終的に、このHammockのソースとRESTEasyのCdiInjectorFactory、WeldのEnhancedListenerを見ていたら実現できそうな感じがしたので、JNDIはやめてこの形態になりました。
あと、
@kazuhira_r 課題といえば、作者が飽きちゃったぽいって感じですかね!まあ、多分作者の方も実験的なものだったのかなと。強いていうなら起動方法が Weld! って感じなのが気になるくらいですかね。コードは色々参考になりますURL
2015-04-01 00:19:53 via TweetDeck to @kazuhira_r
これ、たぶんJAX-RSリソースをスキャンするのに手っ取り早いからというのが理由のひとつな気がします…。
Hammockのソースを読んでいて、CDI#currentとかExtensionとかいろいろ参考になりました。けっこう面白かったです。
今回作成したソースコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/embedded-undertow-jaxrs-cdi