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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 13:14:09  来源:igfitidea点击:

Staying DRY with JAX-RS

javajerseyjax-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 fooand quuxare @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 abstractbase class which FooServiceand QuuxServicecould extend:

我设法避免在每个get*方法中重复注入参数。1这是一个好的开始,但我也希望能够避免跨资源类的重复。与CDI的工作(这也是我需要)是使用的方法abstract的基础类FooServiceQuuxService可能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 utilfield is not null. Unfortunately, the JAX-RS injection does notwork; idand xyzare nullin the get*methods of FooServiceand QuuxService.

get*方法内部,CDI 注入(奇迹般地)正常工作:该util字段不为空。不幸的是,JAX-RS的注射却不能正常工作; id并且xyznullget*方法FooServiceQuuxService

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 @PathParams (etc.) into subclasses is a bug or just part of the JAX-RS spec.

鉴于 CDI 按我的意愿工作,我想知道将@PathParams(等)注入子类的失败是错误还是只是 JAX-RS 规范的一部分。



Another approach I have already tried is using BaseServiceas a single point of entry that delegates to FooServiceand QuuxServiceas needed. This is basically as described in RESTful Java with JAX-RSusing subresource locators.

我已经尝试另一种方法是使用BaseService作为入门委托给单点FooServiceQuuxService需要。这基本上如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 @Injectan instance into FooServicebut 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:

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, @QueryParamor any other param, you can use @Context UriInfoto 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 getIdParameteris 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
}