CLOVER🍀

That was when it all began.

ScalaとJAX-RSとコンストラクタインジェクションと

こちらの資料を見て、以下の2枚が気になりまして。

Kotlin + JAX-RS
http://backpaper0.github.io/ghosts/kotlin-jaxrs.html#/9
http://backpaper0.github.io/ghosts/kotlin-jaxrs.html#/10

元ネタはKotlinですが、コンストラクタインジェクションをしようとしてうまくいかないという話。

これ、Scalaなら普通に動くんじゃ?と思い、簡単に実装。
src/main/scala/org/littlewings/javaee7/rest/HelloResource.scala

package org.littlewings.javaee7.rest

import javax.ws.rs.{DefaultValue, GET, Path, Produces, QueryParam}
import javax.ws.rs.core.{Context, MediaType, UriInfo}

@Path("hello")
class HelloResource(@Context uriInfo: UriInfo) {
  @GET
  @Produces(Array(MediaType.TEXT_PLAIN))
  def sayHello(@QueryParam("name") @DefaultValue("world") name: String): String =
    s"Hello, $name!"

  @GET
  @Path("path")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def path: String =
    uriInfo.getPath
}

@Path("hello-val")
class HelloValResource(@Context val uriInfo: UriInfo) {
  @GET
  @Produces(Array(MediaType.TEXT_PLAIN))
  def sayHello(@QueryParam("name") @DefaultValue("world") name: String): String =
    s"Hello, $name!"

  @GET
  @Path("path")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def path: String =
    uriInfo.getPath
}

まあ、val付けようが付けまいが動きますよね。

このように展開されますし。
コンパイルオプションに「-Xprint:jvm」を追加

  @javax.ws.rs.Path(value = "hello") class HelloResource extends Object {
    @javax.ws.rs.core.Context <paramaccessor> private[this] val uriInfo: javax.ws.rs.core.UriInfo = _;
    @javax.ws.rs.GET @javax.ws.rs.Produces(value = ["text/plain"]) def sayHello(@javax.ws.rs.QueryParam(value = "name") @javax.ws.rs.DefaultValue(value = "world") name: String): String = new StringContext(scala.this.Predef.wrapRefArray(Array[String]{"Hello, ", "!"}.$asInstanceOf[Array[Object]]())).s(scala.this.Predef.genericWrapArray(Array[Object]{name}));
    @javax.ws.rs.GET @javax.ws.rs.Path(value = "path") @javax.ws.rs.Produces(value = ["text/plain"]) def path(): String = HelloResource.this.uriInfo.getPath();
    def <init>(@javax.ws.rs.core.Context uriInfo: javax.ws.rs.core.UriInfo): org.littlewings.javaee7.rest.HelloResource = {
      HelloResource.this.uriInfo = uriInfo;
      HelloResource.super.<init>();
      ()
    }
  };
  @javax.ws.rs.Path(value = "hello-val") class HelloValResource extends Object {
    <paramaccessor> private[this] val uriInfo: javax.ws.rs.core.UriInfo = _;
    <stable> <accessor> <paramaccessor> def uriInfo(): javax.ws.rs.core.UriInfo = HelloValResource.this.uriInfo;
    @javax.ws.rs.GET @javax.ws.rs.Produces(value = ["text/plain"]) def sayHello(@javax.ws.rs.QueryParam(value = "name") @javax.ws.rs.DefaultValue(value = "world") name: String): String = new StringContext(scala.this.Predef.wrapRefArray(Array[String]{"Hello, ", "!"}.$asInstanceOf[Array[Object]]())).s(scala.this.Predef.genericWrapArray(Array[Object]{name}));
    @javax.ws.rs.GET @javax.ws.rs.Path(value = "path") @javax.ws.rs.Produces(value = ["text/plain"]) def path(): String = HelloValResource.this.uriInfo().getPath();
    def <init>(@javax.ws.rs.core.Context uriInfo: javax.ws.rs.core.UriInfo): org.littlewings.javaee7.rest.HelloValResource = {
      HelloValResource.this.uriInfo = uriInfo;
      HelloValResource.super.<init>();
      ()
    }
  }
}

コンストラクタ引数にアノテーションは付きますが

    // HelloResource
    def <init>(@javax.ws.rs.core.Context uriInfo: javax.ws.rs.core.UriInfo): org.littlewings.javaee7.rest.HelloResource = {
      HelloResource.this.uriInfo = uriInfo;
      HelloResource.super.<init>();
      ()
    }

    // HelloValResource
    def <init>(@javax.ws.rs.core.Context uriInfo: javax.ws.rs.core.UriInfo): org.littlewings.javaee7.rest.HelloValResource = {
      HelloValResource.this.uriInfo = uriInfo;
      HelloValResource.super.<init>();
      ()
    }

フィールドには…あれ?valが無い方は付いてますね。

    // HelloResource
    @javax.ws.rs.core.Context <paramaccessor> private[this] val uriInfo: javax.ws.rs.core.UriInfo = _;

    // HelloValResource
    <paramaccessor> private[this] val uriInfo: javax.ws.rs.core.UriInfo = _;
    <stable> <accessor> <paramaccessor> def uriInfo(): javax.ws.rs.core.UriInfo = HelloValResource.this.uriInfo;

それでも動くんですね。

JDでデコンパイルしてみました。

package org.littlewings.javaee7.rest;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import scala.Predef.;
import scala.StringContext;
import scala.reflect.ScalaSignature;

@Path("hello")
@ScalaSignature(bytes="\006\001u3A!\001\002\001\027\ti\001*\0327m_J+7o\\;sG\026T!a\001\003\002\tI,7\017\036\006\003\013\031\tqA[1wC\026,wG\003\002\b\021\005YA.\033;uY\026<\030N\\4t\025\005I\021aA8sO\016\0011C\001\001\r!\ti\001#D\001\017\025\005y\021!B:dC2\f\027BA\t\017\005\031\te.\037*fM\"A1\003\001B\001B\003%A#A\004ve&LeNZ8\021\005UqR\"\001\f\013\005]A\022\001B2pe\026T!!\007\016\002\005I\034(BA\016\035\003\t98OC\001\036\003\025Q\027M^1y\023\tybCA\004Ve&LeNZ8)\005I\t\003CA\013#\023\t\031cCA\004D_:$X\r\037;\t\013\025\002A\021\001\024\002\rqJg.\033;?)\t9\023\006\005\002)\0015\t!\001C\003\024I\001\007A\003\013\002*C!)A\006\001C\001[\005A1/Y=IK2dw\016\006\002/kA\021qF\r\b\003\033AJ!!\r\b\002\rA\023X\rZ3g\023\t\031DG\001\004TiJLgn\032\006\003c9AQAN\026A\0029\nAA\\1nK\"\"Q\007\017\037>!\tI$(D\001\031\023\tY\004D\001\007EK\032\fW\017\034;WC2,X-A\003wC2,X-I\001?\003\0259xN\0357eQ\021)\004\tP\"\021\005e\n\025B\001\"\031\005)\tV/\032:z!\006\024\030-\\\021\002m!\"1&\022\037I!\tId)\003\002H1\tA\001K]8ek\016,7\017L\001JC\005Q\025A\003;fqR|\003\017\\1j]\"\0221\006\024\t\003s5K!A\024\r\003\007\035+E\013C\003Q\001\021\005\021+\001\003qCRDW#\001\030)\t=+Eh\025\027\002\023\"\"q*\026\037Y!\tId+\003\002X1\t!\001+\031;iC\005\001\006FA(MQ\021\001Q\013P.\"\003q\013Q\001[3mY>\004")
public class HelloResource
{
  @Context
  private final UriInfo uriInfo;
  
  @GET
  @Produces({"text/plain"})
  public String sayHello(@QueryParam("name") @DefaultValue("world") String name)
  {
    return new StringContext(Predef..MODULE$.wrapRefArray((Object[])new String[] { "Hello, ", "!" })).s(Predef..MODULE$.genericWrapArray(new Object[] { name }));
  }
  
  @GET
  @Path("path")
  @Produces({"text/plain"})
  public String path()
  {
    return this.uriInfo.getPath();
  }
  
  public HelloResource(@Context UriInfo uriInfo) {}
}

やっぱり、両方に@Contextアノテーション付いてますね…。

それでも動くのかな。って、あれ??

一応、作成したソース。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/jaxrs-constructor-inject

追記
結果が妙なことになったので、Kotlin&GlassFishで確認してみました。結果は、こちら。

KotlinでJAX-RSのコンストラクタインジェクションがうまくいかないという話について
http://d.hatena.ne.jp/Kazuhira/20140917/1410976731

なんと、GlassFishだと動かなかったというオチが。しかも、Scalaの方でもGlassFishにするとvalを付与しなかった方は動作しませんでした。