java 使用 JAX-RS 保持干燥
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5875772/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Staying DRY with JAX-RS
提问by Matt Ball
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:
我正在尝试尽量减少许多 JAX-RS 资源处理程序的重复代码,所有这些处理程序都需要一些相同的路径和查询参数。每个资源的基本 url 模板如下所示:
/{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 couldimplement the resource classes like this:
跨资源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 */ }
}
I've managed to avoid repeating the parameter injection into every single get*
method.1This 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
:
我设法避免在每个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 */ }
}
Inside of the get*
methods, the CDI injection (miraculously) works correctly: the util
field is not null. Unfortunately, the JAX-RS injection does notwork; id
and xyz
are null
in the get*
methods of FooService
and QuuxService
.
在get*
方法内部,CDI 注入(奇迹般地)正常工作:该util
字段不为空。不幸的是,JAX-RS的注射却不能正常工作; id
并且xyz
是null
在get*
方法FooService
和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.
鉴于 CDI 按我的意愿工作,我想知道将@PathParam
s(等)注入子类的失败是错误还是只是 JAX-RS 规范的一部分。
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-RSusing subresource locators.
我已经尝试另一种方法是使用BaseService
作为入门委托给单点FooService
和QuuxService
需要。这基本上如RESTful Java with JAX-RSusing 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 meansis 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.
这种方法的缺点是 CDI 注入和 JAX-RS 注入都不适用于子资源类。这样做的原因很明显2,但这意味着我必须手动将字段重新注入子类的构造函数中,这很混乱、丑陋,并且不容易让我自定义进一步的注入。示例:说我想将@Inject
一个实例放入FooService
但不是QuuxService
. 因为我显式实例化了 的子类BaseService
,所以CDI注入不起作用,所以丑陋还在继续。
tl;dr What's the right way to avoid repeatedly injecting fields across JAX-RS resource handler classes?
tl;dr 避免在 JAX-RS 资源处理程序类中重复注入字段的正确方法是什么?
And why aren't inherited fields injected by JAX-RS, while CDI has no issues with this?
为什么不是 JAX-RS 注入的继承字段,而 CDI 对此没有问题?
Edit 1
编辑 1
With a bit of direction from @Tarlog, I think I've found the answer to one of my questions,
在@Tarlog的指导下,我想我已经找到了我的一个问题的答案,
Why aren't inherited fields injected by JAX-RS?
为什么 JAX-RS 不注入继承字段?
In JSR-311 §3.6:
If a subclass or implementation method has any JAX-RS annotations then allof the annotations on the super class or interface method are ignored.
如果子类或实现方法有任何 JAX-RS 注释,则忽略超类或接口方法上的所有注释。
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.
我确信这个决定有一个真正的原因,但不幸的是,在这个特定的用例中,这个事实对我不利。我仍然对任何可能的解决方法感兴趣。
1The caveat with using field-level injection is that I'm now tied to per-request resource class instantiation, but I can live with that.
2Because I'm the one calling new FooService()
rather than the container/the JAX-RS implementation.
1使用字段级注入的警告是我现在依赖于每个请求的资源类实例化,但我可以忍受。
2因为我是调用者new FooService()
而不是容器/JAX-RS 实现。
回答by Lucky
Here is a workaround I'm using:
这是我正在使用的解决方法:
Define a constructor for the BaseService with 'id' and 'xyz' as params:
使用 '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;
}
}
Repeat the constructor on all subclasses with the 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 */ }
}
回答by Gepsens
Looking at Jax's JIRAit seems someone asked for annotation inheritance as milestone for JAX-RS.
看看Jax 的 JIRA,似乎有人要求将注释继承作为 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.
您正在寻找的功能在 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 */ }
}
Or in another workaround :
或者在另一种解决方法中:
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 */ }
}
But seeing how touchy you are, frankly, I doubt your frustration will go away with this ugly code :)
但坦率地说,看到你有多敏感,我怀疑你的挫败感会因这个丑陋的代码而消失:)
回答by Andrei I
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.
我一直有一种感觉,注解继承使我的代码不可读,因为从哪里/如何注入它并不明显(例如,它会在继承树的哪个级别注入以及它在哪里被覆盖(或者它是否被覆盖)全部))。此外,您必须使变量受保护(并且可能不是最终的),这会使超类泄漏其内部状态,并且还可能引入一些错误(至少在调用扩展方法时我总是会问自己:受保护的变量是否在那里发生了变化? ?)。恕我直言,它与 DRY 没有任何关系,因为这不是逻辑的封装,而是注入的封装,这对我来说似乎有些夸张。
At the end I will cite from the JAX-RS spec, 3.6 Annotation Inheritance
最后我将引用 JAX-RS 规范中的3.6 Annotation Inheritance
For consistency with other Java EE specifications, it is recommended to always repeat annotations instead of relying on annotation inheritance.
为了与其他 Java EE 规范保持一致,建议始终重复注解,而不是依赖注解继承。
PS: I admit that I use only sometimes annotation inheritance, but on the method level :)
PS:我承认我有时只使用注解继承,但在方法级别:)
回答by rektide
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
在 RESTEasy 中可以构造一个类,像往常一样用@*Param 进行注释,最后通过对类@Form 进行注释来完成。这个@Form 类可以作为参数注入到任何其他服务的方法调用中。 http://docs.jboss.org/resteasy/docs/2.3.5.Final/userguide/html/_Form.html
回答by Tarlog
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":
避免参数注入的动机是什么?
如果动机是避免重复硬编码的字符串,那么您可以轻松地重命名它们,您可以重用“常量”:
// 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 */ }
}
(Sorry for posting the second answer, but it was too long to put it in a comment of the previous answer)
(很抱歉发布第二个答案,但将其放在上一个答案的评论中太长了)
回答by grzes
You can add a custom provider, particularly via AbstractHttpContextInjectable:
您可以添加自定义提供程序,尤其是通过 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;
}
}
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.
当然,您必须从 HttpContext 以艰难的方式提取路径参数和/或查询参数,但您将在一个地方执行一次。
回答by Tarlog
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:
您可以使用来访问任何类型的参数@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 */ }
}
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.
注意它getIdParameter
是静态的,所以你可以把它放在一些实用程序类中并重用多个类。
UriInfo 保证是线程安全的,因此您可以将资源类保持为单例。
回答by Najeeb Arif
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.
您可以为所有重复参数尝试@BeanParam。因此,而不是每次都注入它们,您可以简单地注入 customBean 就可以了。
Another approach which is more cleaner is that you can inject
另一种更清洁的方法是您可以注入
@Context UriInfo
or
或者
@Context ExtendedUriInfo
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.
到您的资源类,并且在非常方法中您可以简单地访问它们。UriInfo 更加灵活,因为您的 jvm 将少一个 java 源文件来管理,最重要的是 UriInfo 或 ExtendedUriInfo 的单个实例可以让您处理很多事情。
@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
}