使用 JAX-RS 保持干爽编辑 1

2022-08-31 15:26:42

我正在尝试最大限度地减少许多 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 */ }
}

我已经设法避免在每个方法中重复参数注入。1 这是一个良好的开端,但我也希望能够避免跨资源类的重复。与CDI一起使用的一种方法是使用一个基类,它可以:get*abstractFooServiceQuuxServiceextend

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

在方法内部,CDI注入(奇迹般地)工作正常:该字段不为空。不幸的是,JAX-RS注入不起作用; 并且位于 和 的方法中。get*utilidxyznullget*FooServiceQuuxService

是否有针对此问题的修复程序或解决方法?

鉴于 CDI 按照我想要的方式工作,我想知道将 s(等)注入子类的失败是一个错误还是 JAX-RS 规范的一部分。@PathParam


我已经尝试过的另一种方法是用作委派并根据需要使用的单一入口点。这基本上如 RESTful Java 中所描述的那样,JAX-RS 使用子资源定位器。BaseServiceFooServiceQuuxService

// 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,但这意味着我必须手动将字段重新注入子类的构造函数中,这很混乱,丑陋,并且不容易让我自定义进一步的注入。示例:假设我想将一个实例放入,但不是。因为我显式实例化了 的子类,CDI 注入将不起作用,所以丑陋仍在继续。@InjectFooServiceQuuxServiceBaseService


tl;dr 避免在 JAX-RS 资源处理程序类中重复注入字段的正确方法是什么?

为什么 JAX-RS 没有注入继承字段,而 CDI 对此没有问题?


编辑 1

@Tarlog的一些指导下,我想我已经找到了其中一个问题的答案,

为什么 JAX-RS 不注入继承的字段?

JSR-311 §3.6 中

如果子类或实现方法具有任何 JAX-RS 注释,那么将忽略超类或接口方法上的所有注释。

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


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


答案 1

以下是我正在使用的解决方法:

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

答案 2

看看Jax的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 */ }
}

但是,坦率地说,看到你有多敏感,我怀疑你的挫败感会随着这个丑陋的代码而消失:)


推荐