web-dev-qa-db-ja.com

滞在DRY JAX-RSで

いくつかのJAX-RSリソースハンドラーの繰り返しコードを最小限にしようとしています。すべてのハンドラーに同じパスとクエリパラメーターがいくつか必要です。各リソースの基本的なURLテンプレートは次のようになります。

_/{id}/resourceName
_

各リソースには複数のサブリソースがあります。

_/{id}/resourceName/subresourceName
_

したがって、リソース/サブリソースパス(クエリパラメータを含む)は次のようになります。

_/12345/foo/bar?xyz=0
/12345/foo/baz?xyz=0
/12345/quux/abc?xyz=0
/12345/quux/def?xyz=0
_

リソースfooquuxに共通する部分は、@PathParam("id")@QueryParam("xyz")です。 Icouldは、次のようなリソースクラスを実装します。

_// FooService.Java
@Path("/{id}/foo")
public class FooService
{
    @PathParam("id") String id;
    @QueryParam("xyz") String xyz;

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}
_
_// QuuxService.Java
@Path("/{id}/quux")
public class QuxxService
{
    @PathParam("id") String id;
    @QueryParam("xyz") String xyz;

    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}
_

すべての_get*_メソッドへのパラメーターインジェクションの繰り返しを回避することに成功しました。1 これは良いスタートですが、リソースクラス全体で繰り返しが発生しないようにしたいと思います。 CDIで機能するアプローチ(これも必要です)は、abstractおよびFooServiceQuuxServiceできるextend基本クラスを使用することです。

_// BaseService.Java
public abstract class BaseService
{
    // JAX-RS injected fields
    @PathParam("id") protected String id;
    @QueryParam("xyz") protected String xyz;

    // CDI injected fields
    @Inject protected SomeUtility util;
}
_
_// FooService.Java
@Path("/{id}/foo")
public class FooService extends BaseService
{
    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}
_
_// QuuxService.Java
@Path("/{id}/quux")
public class QuxxService extends BaseService
{   
    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}
_

_get*_メソッドの内部では、CDIインジェクションが(奇跡的に)正しく機能します。utilフィールドはnullではありません。残念ながら、JAX-RSインジェクションは機能しませんidおよびxyzの_get*_メソッドでは、nullおよびFooServiceQuuxServiceです。

この問題の修正または回避策はありますか?

CDIが期待どおりに機能することを考えると、_@PathParam_ s(など)をサブクラスに挿入できないのはバグか、それともJAX-RS仕様の一部にすぎないのでしょうか。


私がすでに試したもう1つのアプローチは、BaseServiceを単一のエントリポイントとして使用し、必要に応じてFooServiceおよびQuuxServiceにデリゲートすることです。これは基本的に RESTful Java JAX-RSを使用 サブリソースロケーターの使用で説明されています。

_// BaseService.Java
@Path("{id}")
public class BaseService
{
    @PathParam("id") protected String id;
    @QueryParam("xyz") protected String xyz;
    @Inject protected SomeUtility util;

    public BaseService () {} // default ctor for JAX-RS

    // ctor for manual "injection"
    public BaseService(String id, String xyz, SomeUtility util)
    {
        this.id = id;
        this.xyz = xyz;
        this.util = util;
    }

    @Path("foo")
    public FooService foo()
    {
        return new FooService(id, xyz, util); // manual DI is ugly
    }

    @Path("quux")
    public QuuxService quux()
    {
        return new QuuxService(id, xyz, util); // yep, still ugly
    }
}
_
_// FooService.Java
public class FooService extends BaseService
{
    public FooService(String id, String xyz, SomeUtility util)
    {
        super(id, xyz, util); // the manual DI ugliness continues
    }

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}
_
_// QuuxService.Java
public class QuuzService extends BaseService
{
    public FooService(String id, String xyz, SomeUtility util)
    {
        super(id, xyz, util); // the manual DI ugliness continues
    }

    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}
_

このアプローチの欠点は、CDIインジェクションもJAX-RSインジェクションもサブリソースクラスで機能しないことです。この理由はかなり明白です2、しかしの意味は、フィールドをサブクラスのコンストラクターに手動で再注入する必要があることです。これは、乱雑で、醜く、簡単に追加の注入をカスタマイズさせます。例:インスタンスをFooServiceに_@Inject_したいがQuuxServiceにはしたくないと言います。私はBaseServiceのサブクラスを明示的にインスタンス化しているため、CDIインジェクションは機能せず、醜さは続いています。


tl; dr JAX-RSリソースハンドラークラス全体でフィールドを繰り返し注入しないようにする正しい方法は何ですか?

また、CDIには問題がないのに、継承されたフィールドがJAX-RSによって挿入されないのはなぜですか?


編集1

@ Tarlog からの少しの指示で、私は私の質問の1つに対する答えを見つけたと思います、

継承されたフィールドがJAX-RSによって挿入されないのはなぜですか?

JSR-311§3.6 で:

サブクラスまたは実装メソッドにJAX-RSアノテーションがある場合、スーパークラスまたはインターフェースメソッドのアノテーションのallは無視されます。

この決定には本当の理由があると確信していますが、残念ながら、この特定のユースケースでは、その事実が私に反しています。可能な回避策にまだ興味があります。


1 フィールドレベルのインジェクションを使用する場合の注意点は、要求ごとのリソースクラスのインスタンス化に縛られていることですが、それでも問題ありません。
2 私はコンテナ/ JAX-RS実装ではなくnew FooService()を呼び出しているからです。

62
Matt Ball

これが私が使っている回避策です:

パラメータとして「id」と「xyz」を使用して、BaseServiceのコンストラクタを定義します。

// BaseService.Java
public abstract class BaseService
{
    // JAX-RS injected fields
    protected final String id;
    protected final String xyz;

    public BaseService (String id, String xyz) {
        this.id = id;
        this.xyz = xyz;
    }
}

Injectsを使用してすべてのサブクラスでコンストラクターを繰り返します。

// FooService.Java
@Path("/{id}/foo")
public class FooService extends BaseService
{
    public FooService (@PathParam("id") String id, @QueryParam("xyz") String xyz) {
        super(id, xyz);
    }

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}
6
Lucky

JaxのJIRA を見ると、JAX-RSのマイルストーンとして注釈の継承を要求されたようです。

探している機能はJAX-RSにはまだ存在しませんが、これでうまくいきますか?それは醜いですが、再発注射を防ぎます。

public abstract class BaseService
{
    // JAX-RS injected fields
    @PathParam("id") protected String id;
    @QueryParam("xyz") protected String xyz;

    // CDI injected fields
    @Inject protected SomeUtility util;

    @GET @Path("bar")
    public abstract Response getBar();

    @GET @Path("baz")
    public abstract Response getBaz();

    @GET @Path("abc")
    public abstract Response getAbc();

    @GET @Path("def")
    public abstract Response getDef();
}
// FooService.Java
@Path("/{id}/foo")
public class FooService extends BaseService
{
    public Response getBar() { /* snip */ }

    public Response getBaz() { /* snip */ }
}
// QuuxService.Java
@Path("/{id}/quux")
public class QuxxService extends BaseService
{   
    public Response getAbc() { /* snip */ }

    public Response getDef() { /* snip */ }
}

または別の回避策で:

public abstract class BaseService
{
    @PathParam("id") protected String id;
    @QueryParam("xyz") protected String xyz;

    // CDI injected fields
    @Inject protected SomeUtility util;

    @GET @Path("{stg}")
    public abstract Response getStg(@Pathparam("{stg}") String stg);

}
// FooService.Java
@Path("/{id}/foo")
public class FooService extends BaseService
{
    public Response getStg(String stg) {
        if(stg.equals("bar")) {
              return getBar();
        } else {
            return getBaz();
        }
    }
    public Response getBar() { /* snip */ }

    public Response getBaz() { /* snip */ }
}

しかし、率直に言って、あなたがどれほど厄介であるかを見て、私はあなたの欲求不満がこの醜いコードでなくなるのではないかと思います:)

4
Gepsens

RESTEasyでは、クラスを構築し、通常どおり@ * Paramで注釈を付け、最後にクラス@Formで注釈を付けることができます。この@Formクラスは、他のサービスのメソッド呼び出しへのパラメーターインジェクションになる場合があります。 http://docs.jboss.org/resteasy/docs/2.3.5.Final/userguide/html/_Form.html

3
rektide

いつどこからどのように注入されるか(たとえば、継承ツリーのどのレベルで注入され、どこでオーバーライドされたか(またはオーバーライドされた場所)かがわからないため、アノテーションの継承によってコードが読みにくくなるという感覚が常にありましたすべて))。さらに、変数を保護する必要があります(おそらく最終的なものではありません)。これにより、スーパークラスが内部状態をリークし、いくつかのバグが発生する可能性があります(少なくとも、拡張メソッドを呼び出すときに常に自分自身に質問します。保護された変数がそこで変更されていますか? ?)。これはロジックのカプセル化ではなく、私には誇張されているように見える注入のカプセル化であるため、私見それはDRYには何もありません。

最後に、JAX-RS仕様.6 Annotation Inheritanceから引用します。

他のJava= EE仕様との一貫性を保つために、アノテーションの継承に依存するのではなく、常にアノテーションを繰り返すことをお勧めします。

PS:私は時々アノテーション継承を使用することを認めますが、メソッドレベルでは:)

3
Andrei I

特にAbstractHttpContextInjectableを介して、カスタムプロバイダーを追加できます。

// FooService.Java
@Path("/{id}/foo")
public class FooService
{
    @Context CommonStuff common;

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}


@Provider
public class CommonStuffProvider
    extends AbstractHttpContextInjectable<CommonStuff>
    implements InjectableProvider<Context, Type>
{

    ...

    @Override
    public CommonStuff getValue(HttpContext context)
    {
        CommonStuff c = new CommonStuff();
        c.id = ...initialize from context;
        c.xyz = ...initialize from context;

        return c;
    }
}

もちろん、HttpContextからパスパラメータやクエリパラメータを難しい方法で抽出する必要がありますが、1か所で1回実行する必要があります。

2
grzes

パラメータインジェクションを回避する動機は何ですか?
モチベーションがハードコードされた文字列の繰り返しを避けることであり、簡単に名前を変更できる場合は、「定数」を再利用できます。

// FooService.Java
@Path("/" +  FooService.ID +"/foo")
public class FooService
{
    public static final String ID = "id";
    public static final String XYZ= "xyz";
    public static final String BAR= "bar";

    @PathParam(ID) String id;
    @QueryParam(XYZ) String xyz;

    @GET @Path(BAR)
    public Response getBar() { /* snip */ }

    @GET @Path(BAR)
    public Response getBaz() { /* snip */ }
}

// QuuxService.Java
@Path("/" +  FooService.ID +"/quux")
public class QuxxService
{
    @PathParam(FooService.ID) String id;
    @QueryParam(FooService.XYZ) String xyz;

    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}

(2番目の回答を投稿して申し訳ありませんが、前の回答のコメントに入れるには長すぎました)

1
Tarlog

@BeanParamをすべての繰り返しパラメーターに対して試すことができます。そのため、毎回それらを注入するのではなく、単にトリックを実行するcustomBeanを注入することができます。

よりクリーンな別のアプローチは、あなたが注入することができるということです

@Context UriInfo 

または

@Context ExtendedUriInfo

あなたのリソースクラスに、そして非常にメソッドでそれらに簡単にアクセスできます。 jvmに1つ少ないJava管理するソースファイルが1つ少ないため、UriInfoまたはExtendedUriInfoのすべての単一インスタンスが多くのことのハンドルを提供するため、UriInfoはより柔軟です。

@Path("test")
public class DummyClass{

@Context UriInfo info;

@GET
@Path("/{id}")
public Response getSomeResponse(){
     //custom code
     //use info to fetch any query, header, matrix, path params
     //return response object
}
1
Najeeb Arif

@PathParam@QueryParamまたはその他のパラメーターを使用する代わりに、@Context UriInfoを使用して任意のタイプのパラメーターにアクセスできます。したがって、コードは次のようになります。

// FooService.Java
@Path("/{id}/foo")
public class FooService
{
    @Context UriInfo uriInfo;

    public static String getIdParameter(UriInfo uriInfo) {
        return uriInfo.getPathParameters().getFirst("id");
    }

    @GET @Path("bar")
    public Response getBar() { /* snip */ }

    @GET @Path("baz")
    public Response getBaz() { /* snip */ }
}

// QuuxService.Java
@Path("/{id}/quux")
public class QuxxService
{
    @Context UriInfo uriInfo;

    @GET @Path("abc")
    public Response getAbc() { /* snip */ }

    @GET @Path("def")
    public Response getDef() { /* snip */ }
}

getIdParameterは静的であるため、これをいくつかのユーティリティクラスに入れて、複数のクラスで再利用できるように注意してください。
UriInfoはスレッドセーフであることが保証されているため、リソースクラスをシングルトンとして保持できます。

0
Tarlog