Я пытаюсь минимизировать повторяющийся код для ряда обработчиков ресурсов 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 работает так, как я хотел бы, мне интересно, является ли неспособность внедрить @PathParam
s (и т.д.) в подклассы ошибкой или просто частью спецификации 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 не работает, поэтому уродство продолжается.
С небольшим руководством от @Tarlog, я думаю, что нашел ответ на один из моих вопросов,
Почему наследуемые поля не инъектируются JAX-RS?
В JSR-311 §3.6:
Если подкласс или метод реализации имеет какие-либо аннотации JAX-RS, то все аннотации суперкласса или метода интерфейса игнорируются.
Я уверен, что для такого решения есть реальная причина, но, к сожалению, этот факт работает против меня в данном конкретном случае. Я все еще заинтересован в любых возможных обходных путях.
1 Недостатком использования инъекции на уровне полей является то, что я теперь привязан к инстанцированию класса ресурса на каждый запрос, но я могу с этим жить.
2 Потому что это я вызываю new FooService()
, а не контейнер/реализация JAX-RS.