使用 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
可以扩展< /code>:
// 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
字段不为空。不幸的是,JAX-RS 注入不起作用; FooService
和 的
。get*
方法中的 id
和 xyz
为 null
>QuuxService
这个问题有解决办法或解决方法吗?
鉴于 CDI 按我希望的方式工作,我想知道未能将 @PathParam(等)注入子类是一个错误还是只是 JAX-RS 规范的一部分。
我已经尝试过的另一种方法是使用 BaseService 作为单个入口点,根据需要委托给 FooService 和 QuuxService 。这基本上如 RESTful Java with 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 实现的人。
I'm trying to minimize repeated code for a number of JAX-RS resource handlers, all of which require a few of the same path and query parameters. The basic url template for each resource looks like this:
/{id}/resourceName
and each resource has multiple subresources:
/{id}/resourceName/subresourceName
So, resource/subresource paths (incl. query parameters) might look like
/12345/foo/bar?xyz=0
/12345/foo/baz?xyz=0
/12345/quux/abc?xyz=0
/12345/quux/def?xyz=0
The common parts across resources foo
and quux
are @PathParam("id")
and @QueryParam("xyz")
. I could implement the resource classes like this:
// 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 */ }
}
I've managed to avoid repeating the parameter injection into every single get*
method.1 This is a good start, but I'd like to be able to avoid the repetition across resource classes as well. An approach that works with CDI (which I also need) is to use an abstract
base class which FooService
and QuuxService
could 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 */ }
}
Inside of the get*
methods, the CDI injection (miraculously) works correctly: the util
field is not null. Unfortunately, the JAX-RS injection does not work; id
and xyz
are null
in the get*
methods of FooService
and QuuxService
.
Is there a fix or workaround for this problem?
Given that the CDI works as I'd like it to, I'm wondering if the failure to inject @PathParam
s (etc.) into subclasses is a bug or just part of the JAX-RS spec.
Another approach I have already tried is using BaseService
as a single point of entry that delegates to FooService
and QuuxService
as needed. This is basically as described in RESTful Java with JAX-RS using subresource locators.
// 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 */ }
}
The downside to this approach is that neither CDI injection nor JAX-RS injection works in the subresource classes. The reason for this is fairly obvious2, but what that means is that I have to manually re-inject the fields into the subclasses' constructor, which is messy, ugly, and doesn't easily let me customize further injection. Example: say I wanted to @Inject
an instance into FooService
but not QuuxService
. Because I'm explicitly instantiating the subclasses of BaseService
, CDI injection won't work, so the ugliness is continued.
tl;dr What's the right way to avoid repeatedly injecting fields across JAX-RS resource handler classes?
And why aren't inherited fields injected by JAX-RS, while CDI has no issues with this?
Edit 1
With a bit of direction from @Tarlog, I think I've found the answer to one of my questions,
Why aren't inherited fields injected by JAX-RS?
In JSR-311 §3.6:
If a subclass or implementation method has any JAX-RS annotations then all of the annotations on the super class or interface method are ignored.
I'm sure that there's a real reason for this decision, but unfortunately that fact is working against me in this particular use case. I'm still interested in any possible workarounds.
1 The caveat with using field-level injection is that I'm now tied to per-request resource class instantiation, but I can live with that.
2 Because I'm the one calling new FooService()
rather than the container/the JAX-RS implementation.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
这是我正在使用的解决方法:
使用“id”和“xyz”作为参数定义 BaseService 的构造函数:
使用注入在所有子类上重复构造函数:
Here is a workaround I'm using:
Define a constructor for the BaseService with 'id' and 'xyz' as params:
Repeat the constructor on all subclasses with the injects:
查看 Jax 的 JIRA 似乎有人要求将注释继承作为 JAX-RS 的里程碑。
您正在寻找的功能在 JAX-RS 中尚不存在,但是,这行得通吗?
它很丑,但可以防止重复注射。
或者在另一个解决方法中:
但坦率地说,看到你有多敏感,我怀疑你的挫败感会因为这段丑陋的代码而消失:)
Looking at Jax's JIRA it seems someone asked for annotation inheritance as milestone for JAX-RS.
The feature you're looking for just doesn't exist in JAX-RS yet, however, would this work?
It's ugly, but prevents recurrent injection.
Or in another workaround :
But seeing how touchy you are, frankly, I doubt your frustration will go away with this ugly code :)
在 RESTEasy 中,我们可以构造一个类,像往常一样用 @*Param 进行注释,最后通过对类 @Form 进行注释来完成。然后,这个 @Form 类可以作为参数注入到任何其他服务的方法调用中。
http://docs.jboss.org/resteasy /docs/2.3.5.Final/userguide/html/_Form.html
In RESTEasy one can construct a class, annotate with @*Param as usual, and finish by annotating the class @Form. This @Form class may then be a parameter injection into any other service's method call.
http://docs.jboss.org/resteasy/docs/2.3.5.Final/userguide/html/_Form.html
我总是有一种感觉,注释继承使我的代码不可读,因为从哪里/如何注入它并不明显(例如,它将被注入到继承树的哪个级别以及它被覆盖在哪里(或者是在全部))。此外,您必须使变量受保护(并且可能不是最终的),这使得超类泄漏其内部状态,并且还可能引入一些错误(至少在调用扩展方法时我总是会问自己:受保护的变量是否已更改) ?)。恕我直言,它与 DRY 没有任何关系,因为这不是逻辑的封装,而是注入的封装,这对我来说似乎有点夸张。
最后我将引用 JAX-RS 规范中的3.6 Annotation Inheritance
PS:我承认我有时只使用注释继承,但在方法级别:)
I always had a feeling, that annotation inheritance makes my code unreadable, as it is not obvious from where/how it is injected (e.g on which level of the inheritance tree would it be injected and where was it overriden (or was it overriden at all)). Moreover, you have to make the variable protected (and probably NOT final), which makes the superclass leak its internal state and also may introduce some bugs ( at least I would always ask myself when calling an extended method: is the protected variable changed there?). IMHO it has nothing with DRY, as this is not encapsulation of logic, but encapsulation of injection, which seems exaggerated to me.
At the end I will cite from the JAX-RS spec, 3.6 Annotation Inheritance
PS: I admit that I use only sometimes annotation inheritance, but on the method level :)
避免参数注入的动机是什么?
如果动机是避免重复硬编码字符串,因此您可以轻松地重命名它们,则可以重用“常量”:(
很抱歉发布第二个答案,但太长了,无法将其放在上一个答案的评论中)
What is the motivation of avoiding parameters injections?
If the motivation is avoiding of repeating hard-coded strings, so you can easily rename them, you can reuse "constants":
(Sorry for posting the second answer, but it was too long to put it in a comment of the previous answer)
您可以添加自定义提供程序,特别是通过 AbstractHttpContextInjectable:
当然,您必须以困难的方式从 HttpContext 中提取路径参数和/或查询参数,但您将在一个地方完成一次。
You can add a custom provider, particularly via AbstractHttpContextInjectable:
Granted, you'll have to extract the path parameters and/or the query parameters the hard way from HttpContext, but you'll do it once in one place.
您可以使用
@Context UriInfo
来访问任何类型的参数,而不是使用@PathParam
、@QueryParam
或任何其他参数。因此您的代码可以是:请注意
getIdParameter
是静态的,因此您可以将其放入某个实用程序类中并在多个类中重用。UriInfo 保证是线程安全的,因此您可以将资源类保持为单例。
Instead of using
@PathParam
,@QueryParam
or any other param, you can use@Context UriInfo
to access any types of parameters. So your code could be:Pay attention that
getIdParameter
is static, so you can put it in some utility class and reuse accorss multiple classes.UriInfo is guaranteed to be threadsafe, so you can keep resource class as singleton.
您可以尝试 @BeanParam 所有重复参数。因此,您不必每次都注入它们,只需注入 customBean 就可以了。
另一种更简洁的方法是您可以将
或
注入到您的资源类中,并且通过这种方法您可以简单地访问它们。
UriInfo 更加灵活,因为您的 jvm 将少一个要管理的 java 源文件,而且最重要的是,UriInfo 或 ExtendedUriInfo 的单个实例可以让您处理很多事情。
You can try @BeanParam for all the repeating params. so rather than injecting them every time you can simply inject you customBean which will do the trick.
Another approach which is more cleaner is that you can inject
or
to your Resource Class and in very method you can simply access them.
UriInfo is more flexible because your jvm will have one less java source file to manage and above all single instance of UriInfo or ExtendedUriInfo gives you a handle of a lot of things.