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