RESTEasyのEmbedded Containerですが、以前JDK HttpServerとUndertowを試していましたのですけれど、Nettyがいるのが最近気になっていまして、ちょっと遊んでみることにしました。
Chapter 36. Embedded Containers / 36.4. Netty
ただ、ドキュメントに載っているのはNetty 3のみで、実際にはNetty関連のRESTEasyのモジュールとしては、以下の3種類があります。
- resteasy-netty(Netty 3)
- resteasy-netty4(Netty 4)
- resteasy-netty4(Netty 4+CDI)
最後のは、CDIサポートが付いたものですね。
順番に、試していってみます。なお、コードはScalaで書きます。
Netty 3
最初は、Netty 3から。依存関係は、このように定義。
libraryDependencies ++= Seq( "org.jboss.resteasy" % "resteasy-netty" % "3.0.13.Final", "org.scalatest" %% "scalatest" % "2.2.5" % "test" )
ScalaTestがいるのは、テストコード用です。
JAX-RS動作用に作成したコードは、こちら。
JAX-RS有効化。
src/main/scala/org/littlewings/javaee7/rest/JaxrsApplication.scala
package org.littlewings.javaee7.rest import scala.collection.JavaConverters._ import javax.ws.rs.ApplicationPath import javax.ws.rs.core.Application @ApplicationPath("rest") class JaxrsApplication extends Application { override def getClasses: java.util.Set[Class[_]] = Set[Class[_]](classOf[CalcResource]).asJava }
リソースクラス。
src/main/scala/org/littlewings/javaee7/rest/CalcResource.scala
package org.littlewings.javaee7.rest import javax.ws.rs.core.MediaType import javax.ws.rs.{GET, Path, Produces, QueryParam} @Path("calc") class CalcResource { @GET @Path("add") @Produces(value = Array(MediaType.TEXT_PLAIN)) def add(@QueryParam("a") a: Int, @QueryParam("b") b: Int): Int = a + b }
テストコード。
src/test/scala/org/littlewings/javaee7/rest/RestEasyNetty3Spec.scala
package org.littlewings.javaee7.rest import org.jboss.resteasy.plugins.server.netty.NettyJaxrsServer import org.jboss.resteasy.spi.ResteasyDeployment import org.scalatest.FunSpec import org.scalatest.Matchers._ import scala.io.Source class RestEasyNetty3Spec extends FunSpec { describe("RestEasyNetty3Spec") { it("calc/add, using Application") { val netty = new NettyJaxrsServer val deployment = netty.getDeployment // 以下も可 // val deployment = new ResteasyDeployment deployment.setApplicationClass(classOf[JaxrsApplication].getName) netty.setRootResourcePath("") netty.setPort(8080) netty.setDeployment(deployment) netty.start() val source = Source.fromURL("http://localhost:8080/calc/add?a=5&b=3") source.mkString.toInt should be(8) source.close() netty.stop() } it("calc/add, Resource only") { val netty = new NettyJaxrsServer val deployment = netty.getDeployment netty.setRootResourcePath("") netty.setPort(8080) netty.setDeployment(deployment) netty.start() deployment.getRegistry.addPerRequestResource(classOf[CalcResource]) val source = Source.fromURL("http://localhost:8080/calc/add?a=5&b=3") source.mkString.toInt should be(8) source.close() netty.stop() } } }
アプリケーションをデプロイするクラスとしては、NettyJaxrsServerというものを使います。
val netty = new NettyJaxrsServer
ResteasyDeploymentというクラスに、Applicationのサブクラスを設定したり
※ServletInitializerがないので、Applicationのサブクラスはリソースクラスの情報を返す必要があります
val deployment = netty.getDeployment
deployment.setApplicationClass(classOf[JaxrsApplication].getName)
リソースクラス自体を登録したりします。
deployment.getRegistry.addPerRequestResource(classOf[CalcResource])
NettyJaxrsServerに対して、ポートやパスの設定をして、ResteasyDeploymentをデプロイします。
netty.setRootResourcePath("") netty.setPort(8080) netty.setDeployment(deployment) netty.start()
で、最後にstartを呼び出す、と。
以下の記述を行う場合は、NettyJaxrsServer#startの後でなければ意味がないようです。
deployment.getRegistry.addPerRequestResource(classOf[CalcResource])
なお、ResteasyDeploymentは今回NettyJaxrsServer#getDeploymentから取得していますが、newしても構わないようです。
val deployment = new ResteasyDeployment
NettyJaxrsServerの中を見ると、startするまではnewしたResteasyDeploymentを定義しているだけみたいですし。
Netty 4
続いて、Netty 4を使う場合。ライブラリの依存関係は、このようになります。
libraryDependencies ++= Seq( "org.jboss.resteasy" % "resteasy-netty4" % "3.0.13.Final", "io.netty" % "netty-all" % "4.0.32.Final", "org.scalatest" %% "scalatest" % "2.2.5" % "test" )
resteasy-netty4以外にも、Netty自身が必要です。resteasy-netty4では、Nettyへの依存関係はprovidedになっていますので。
主な変更点は、以上です。
Netty 3向けのコードが、そのまま動作します。
まあ、作ったテストコードのクラス名を変えた程度ですね。
class RestEasyNetty4Spec extends FunSpec { describe("RestEasyNetty4Spec") {
Netty 4+CDI
最後は、Netty 4にCDIを加えたもの。
libraryDependencies ++= Seq( "org.jboss.resteasy" % "resteasy-cdi" % "3.0.13.Final", "org.jboss.resteasy" % "resteasy-netty4-cdi" % "3.0.13.Final", "io.netty" % "netty-all" % "4.0.32.Final", "org.jboss.weld.se" % "weld-se" % "2.3.1.Final", "org.scalatest" %% "scalatest" % "2.2.5" % "test" )
依存関係に、resteasy-cdiとWeld SEを追加しました。
Weld SE??って感じではありますが、Weld Servletを使うのは、今回はパスしました…。
このエントリを書いた当時は使い方をちょっと間違っていたのですが、@RequestScopedが使えるようです。
@SessionScopedはダメな気がします…
※2016/12/12追記
CDIを使うということで、JAX-RS用の各種クラスはこのように修正しました。
JAX-RS有効化。リソースクラスの返却はやめました。
src/main/scala/org/littlewings/javaee7/rest/JaxrsApplication.scala
package org.littlewings.javaee7.rest import javax.ws.rs.ApplicationPath import javax.ws.rs.core.Application @ApplicationPath("rest") class JaxrsApplication extends Application
リソースクラス。@RequestScopedなクラスとし、別途CDI管理Beanをインジェクションしています。
src/main/scala/org/littlewings/javaee7/rest/CalcResource.scala
package org.littlewings.javaee7.rest import javax.enterprise.context.RequestScoped import javax.inject.Inject import javax.ws.rs.core.MediaType import javax.ws.rs.{GET, Path, Produces, QueryParam} @Path("calc") @RequestScoped class CalcResource { @Inject private var calcService: CalcService = _ @GET @Path("add") @Produces(value = 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/rest/CalcService.scala
package org.littlewings.javaee7.rest import javax.enterprise.context.ApplicationScoped @ApplicationScoped class CalcService { def add(a: Int, b: Int): Int = a + b }
あと、beans.xmlも必要です。
src/main/resources/META-INF/beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="annotated"> </beans>
で、テストコードはこのようになりました。
src/test/scala/org/littlewings/javaee7/rest/RestEasyNetty4CdiSpec.scala
package org.littlewings.javaee7.rest import javax.enterprise.inject.spi.CDI import org.jboss.resteasy.cdi.{CdiInjectorFactory, ResteasyCdiExtension} import org.jboss.resteasy.plugins.server.netty.cdi.CdiNettyJaxrsServer import org.jboss.resteasy.spi.ResteasyDeployment import org.jboss.weld.environment.se.Weld import org.scalatest.FunSpec import org.scalatest.Matchers._ import scala.io.Source class RestEasyNetty4CdiSpec extends FunSpec { describe("RestEasyNetty4CdiSpec") { it("calc/add, using Application") { val weld = new Weld weld.initialize() val cdiExtension = CDI.current().select(classOf[ResteasyCdiExtension]).get val netty = new CdiNettyJaxrsServer val deployment = netty.getDeployment // 以下も可 // val deployment = new ResteasyDeployment deployment.setActualResourceClasses(cdiExtension.getResources) deployment.setInjectorFactoryClass(classOf[CdiInjectorFactory].getName) deployment.setActualProviderClasses(cdiExtension.getProviders) netty.setRootResourcePath("") netty.setPort(8080) netty.setDeployment(deployment) netty.start() val source = Source.fromURL("http://localhost:8080/calc/add?a=5&b=3") source.mkString.toInt should be(8) source.close() netty.stop() weld.shutdown() } it("calc/add, Resource only") { val weld = new Weld weld.initialize() val cdiExtension = CDI.current().select(classOf[ResteasyCdiExtension]).get val netty = new CdiNettyJaxrsServer val deployment = netty.getDeployment deployment.setActualResourceClasses(cdiExtension.getResources) deployment.setInjectorFactoryClass(classOf[CdiInjectorFactory].getName) netty.setRootResourcePath("") netty.setPort(8080) netty.setDeployment(deployment) netty.start() val source = Source.fromURL("http://localhost:8080/calc/add?a=5&b=3") source.mkString.toInt should be(8) source.close() netty.stop() weld.shutdown() } } }
最初と最後に、Weld SEの初期化/終了が入りました。
val weld = new Weld weld.initialize() weld.shutdown()
resteasy-netty4-cdiのテストコードをマネてですが、Applicationのサブクラスやリソースクラスの収集は、ResteasyCdiExtensionクラスを利用しています。
また、NettyJaxrsServerに代わりCdiNettyJaxrsServerを使用してサーバーを起動することで、@RequestScopedが使えるようになります。
val cdiExtension = CDI.current().select(classOf[ResteasyCdiExtension]).get val netty = new CdiNettyJaxrsServer val deployment = netty.getDeployment // 以下も可 // val deployment = new ResteasyDeployment deployment.setActualResourceClasses(cdiExtension.getResources) deployment.setInjectorFactoryClass(classOf[CdiInjectorFactory].getName) deployment.setActualProviderClasses(cdiExtension.getProviders)
CdiInjectorFactoryクラスを設定しておくことも必要です。
まとめ
RESTEasyを、Netty 3、4で動かすサンプルを書いてみました。ドキュメントの紹介はNetty 3でしたが、依存関係をちょっと調整するだけでNetty 4にも乗せられ、さらにCDIも使えますよ、と。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/resteasy-embedded-netty3
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/resteasy-embedded-netty4
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/resteasy-embedded-netty4-cdi