使用JAX-RS保持干爽

时间:2011-05-03 21:12:44

标签: java jersey 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

资源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注入工作;在idxyz的{​​{1}}方法中,nullget*FooService

此问题是否有修复或解决方法?

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


我已尝试过的另一种方法是使用@PathParam作为单一条目,根据需要委托给BaseServiceFooService。这基本上如RESTful Java with JAX-RS中使用子资源定位器所述。

QuuxService

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

这种方法的缺点是CDI注入和JAX-RS注入都不适用于子资源类。这个的原因是相当明显的 2 ,但意味着什么是我必须手动将字段重新注入子类的构造函数,这是一个混乱,丑陋,并且不容易让我自定义进一步注射。示例:说我想// 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 */ } } 将一个实例导入@Inject但不是FooService。因为我明确地实例化了QuuxService的子类,所以CDI注入不起作用,所以丑陋仍在继续。


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

为什么不是由JAX-RS注入的继承字段,而CDI对此没有任何问题?


编辑1

根据@Tarlog的一点指示,我想我已经找到了我的一个问题的答案,

  

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

JSR-311 §3.6中:

  

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

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


1 使用字段级注入的警告是我现在与每个请求的资源类实例化相关联,但我可以接受它。
2 因为我是那个调用BaseService而不是容器/ JAX-RS实现的人。

8 个答案:

答案 0 :(得分:6)

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

为BaseService定义一个构造函数,其中'id'和'xyz'为params:

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

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

答案 1 :(得分:4)

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 :(得分:3)

在RESTEasy中,可以构建一个类,像往常一样用@ * Param注释,并通过注释类@Form来完成。然后,这个@Form类可以是注入任何其他服务的方法调用的参数。 http://docs.jboss.org/resteasy/docs/2.3.5.Final/userguide/html/_Form.html

答案 3 :(得分:3)

我总是有一种感觉,注释继承使我的代码不可读,因为注入的位置/方式并不明显(例如,它将被注入哪个级别的继承树以及它被覆盖的位置(或者是它完全覆盖))。此外,你必须使变量受保护(并且可能不是最终的),这使得超类泄漏其内部状态并且还可能引入一些错误(至少我会在调用扩展方法时总是问自己:受保护的变量是否在那里改变?)。恕我直言,它与DRY没有任何关系,因为这不是逻辑的封装,而是注入的封装,这对我来说似乎有些夸大。

最后我将引用JAX-RS规范, 3.6 Annotation Inheritance

  

为了与其他Java EE规范保持一致,建议使用   始终重复注释而不是依赖注释   继承。

PS:我承认我只使用有时注释继承,但在方法层面上:)

答案 4 :(得分: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中提取路径参数和/或查询参数,但是您将在一个地方执行一次。

答案 5 :(得分:1)

避免参数注射的动机是什么?
如果动机是避免重复硬编码字符串,那么你可以轻松地重命名它们,你可以重用“常量”:

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

(很抱歉发布了第二个答案,但是将它放在上一个答案的评论中太长了)

答案 6 :(得分:1)

您可以尝试@BeanParam用于所有重复参数。所以,不要每次只注入customBean时注入它们,这样就可以解决问题。

另一种更清洁的方法是你可以注入

@Context UriInfo 

@Context ExtendedUriInfo

到您的资源类,在非常方法中,您只需访问它们。 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
}

答案 7 :(得分:0)

您可以使用@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 */ }
}

注意getIdParameter是静态的,因此您可以将它放在某个实用程序类中,并重用多个类。
UriInfo保证是线程安全的,因此您可以将资源类保持为单例。