有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java使用JAXR保持干燥

我正在尝试最小化许多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

跨资源fooquux的公共部分是@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基类,它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 */ }
}

get*方法内部,CDI注入(奇迹般地)工作正常:util字段不是空的。不幸的是,JAX-RS注入不起作用id和{}在{}和{}的{}方法中是{}

这个问题有解决方法吗

鉴于CDI的工作方式符合我的要求,我想知道将@PathParams(等)注入子类的失败是一个错误,还是只是JAX-RS规范的一部分


我已经尝试过的另一种方法是使用BaseService作为单个入口点,根据需要委托给FooServiceQuuxService。这基本上如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的一点指导,我想我已经找到了我的一个问题的答案

Why aren't inherited fields injected by JAX-RS?

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.

我确信这个决定有一个真正的原因,但不幸的是,这个事实在这个特定的用例中对我不利。我仍然对任何可能的解决办法感兴趣


1使用字段级注入的注意事项是,我现在被绑定到每个请求的资源类实例化,但我可以接受这一点
2因为我是调用new FooService()而不是容器/JAX-RS实现的人


共 (4) 个答案

  1. # 1 楼答案

    看看Jax's JIRA似乎有人要求将注释继承作为JAX-RS的里程碑

    您正在寻找的功能在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 */ }
    }
    

    或者在另一个解决方案中:

    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 */ }
    }
    

    但坦率地说,看到你这么敏感,我怀疑你的沮丧情绪会随着这段难看的代码而消失:)

  2. # 2 楼答案

    您可以添加自定义提供程序,尤其是通过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;
        }
    }
    

    当然,您必须从HttpContext中艰难地提取路径参数和/或查询参数,但您只能在一个地方提取一次

  3. # 3 楼答案

    下面是我正在使用的一个变通方法:

    为BaseService定义一个构造函数,将“id”和“xyz”作为参数:

    // 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;
        }
    }
    

    在所有具有注入的子类上重复构造函数:

    // 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 */ }
    }
    
  4. # 4 楼答案

    我一直有一种感觉,注释继承使我的代码不可读,因为从注入的位置/方式来看,这并不明显(例如,将注入到继承树的哪个级别,在哪里被重写(或者是否被重写))。此外,您必须保护变量(可能不是最终的),这会使超类泄漏其内部状态,还可能引入一些错误(至少在调用扩展方法时,我总是会问自己:受保护的变量在那里更改了吗?)。IMHO与DRY无关,因为这不是逻辑的封装,而是注入的封装,这在我看来有些夸张

    最后,我将引用JAX-RS规范中的3.6注释继承

    For consistency with other Java EE specifications, it is recommended to always repeat annotations instead of relying on annotation inheritance.

    PS:我承认我有时只使用注释继承,但在方法级别:)