Staying DRY with 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

Общими частями для ресурсов foo и quux являются @PathParam("id") и @QueryParam("xyz"). Я смог реализовать классы ресурсов следующим образом:

// 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 базового класса, который FooService и QuuxService могли бы 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 являются null в get* методах FooService и QuuxService.

Есть ли исправление или обходной путь для этой проблемы?

Учитывая, что CDI работает так, как я хотел бы, мне интересно, является ли неспособность внедрить @PathParams (и т.д.) в подклассы ошибкой или просто частью спецификации JAX-RS.


Другой подход, который я уже опробовал, заключается в использовании 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, но это означает, что я должен вручную переинжектировать поля в конструктор подклассов, что грязно, некрасиво и не позволяет легко настроить дальнейшую инжекцию. Пример: допустим, я хочу @Inject экземпляр в FooService, но не в QuuxService. Поскольку я явно инстанцирую подклассы BaseService, инъекция CDI не работает, поэтому уродство продолжается.


tl;dr Какой правильный способ избежать многократной инъекции полей в классах обработчиков ресурсов JAX-RS?

И почему наследуемые поля не инъектируются JAX-RS, в то время как CDI не имеет проблем с этим?


Edit 1

С небольшим руководством от @Tarlog, я думаю, что нашел ответ на один из моих вопросов,

Почему наследуемые поля не инъектируются JAX-RS?

В JSR-311 §3.6:

Если подкласс или метод реализации имеет какие-либо аннотации JAX-RS, то все аннотации суперкласса или метода интерфейса игнорируются.

Я уверен, что для такого решения есть реальная причина, но, к сожалению, этот факт работает против меня в данном конкретном случае. Я все еще заинтересован в любых возможных обходных путях.


1 Недостатком использования инъекции на уровне полей является то, что я теперь привязан к инстанцированию класса ресурса на каждый запрос, но я могу с этим жить.
2 Потому что это я вызываю new FooService(), а не контейнер/реализация JAX-RS.

60
задан Community 23 May 2017 в 12:25
поделиться