与JAX-RS保持干爽
我想尽量减less一些JAX-RS资源处理程序的重复代码,所有这些都需要一些相同的path和查询参数。 每个资源的基本url模板如下所示:
/{id}/resourceName
每个资源都有多个子资源:
/{id}/resourceName/subresourceName
所以,资源/子资源path(包括查询参数)可能看起来像
/12345/foo/bar?xyz=0 /12345/foo/baz?xyz=0 /12345/quux/abc?xyz=0 /12345/quux/def?xyz=0
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 */ } }
我已经设法避免重复参数注入到每一个get*
方法。 1这是一个好的开始,但我希望能够避免跨资源类的重复。 与CDI(我也需要)一起工作的方法是使用FooService
和QuuxService
可以extend
的abstract
基类:
// 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
和xyz
在FooService
和QuuxService
的get*
方法中为null
。
有没有修复或解决这个问题?
鉴于CDI的工作,我想知道,如果未能注入@PathParam
s(等)到子类是一个错误或只是JAX-RS规范的一部分。
我已经尝试过的另一种方法是使用BaseService
作为根据需要委托给FooService
和QuuxService
的单一入口点。 这基本上就像使用子资源定位器的RESTful Java与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 ,但是这意味着我必须手动将字段重新注入到子类的构造函数中,这很麻烦,丑陋,并且不容易让我定制进一步的注入。 例如:说我想@注入一个实例到FooService
而不是QuuxService
。 因为我明确地实例化了BaseService
的子类,所以CDI注入将不起作用,所以丑是继续的。
tl; dr避免在JAX-RS资源处理程序类中重复注入字段的正确方法是什么?
为什么没有被JAX-RS注入的inheritance域,而CDI没有这个问题呢?
编辑1
从@Tarlog的一点点方向,我想我已经find了我的一个问题的答案,
为什么没有被JAX-RS注入的inheritance域?
在JSR-311§3.6中 :
如果子类或实现方法具有任何JAX-RS注释,则将忽略超类或接口方法上的所有注释。
我相信这个决定有一个真正的原因,但不幸的是,这个事实在这个特定的用例中对我不利。 我仍然对任何可能的解决方法感兴趣。
1使用字段级注入的警告是,我现在被绑定到每个请求资源类的实例,但我可以忍受。
2因为我是调用new FooService()
而不是容器/ JAX-RS的实现。
这是我正在使用的解决方法:
使用'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; } }
用注入重复所有子类的构造函数:
// 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 */ } }
看着Jax的JIRA ,似乎有人要求注释inheritance作为JAX-RS的里程碑。
你正在寻找的function在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 */ } }
但看到你是多么的敏感,坦率地说,我怀疑你的挫败感会消失在这个丑陋的代码:)
在RESTEasy中,可以构build一个类,像往常一样使用@ * Param进行注释,并通过注释类@Form来完成。 这个@Form类可能是一个参数注入任何其他服务的方法调用。 http://docs.jboss.org/resteasy/docs/2.3.5.Final/userguide/html/_Form.html
您可以添加一个自定义提供程序,特别是通过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中提取path参数和/或查询参数,但是你只能在一个地方完成。
我总是有一种感觉,那个注释inheritance使我的代码变得不可读,因为从注入的地方/注入方式(例如注入inheritance树的哪个层次,它被覆盖的地方是什么)并不明显(或者被覆盖所有))。 此外,你必须使variables受保护(可能不是最终),这使得超类泄漏其内部状态,也可能引入一些错误(至less我总是问自己,当调用扩展方法:被保护的variables是否改变了?)。 恕我直言,它与DRY没有任何关系,因为这不是逻辑封装,而是封装注入,这似乎是夸大了我。
最后我会引用JAX-RS规范3.6注释inheritance
为了与其他Java EE规范保持一致,build议始终重复注释而不是依赖注释inheritance。
PS:我承认我只使用有时注释inheritance,但在方法级别:)
避免参数注入的动机是什么?
如果动机是避免重复硬编码的string,所以你可以轻松地重命名它们,你可以重用“常量”:
// 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 */ } }
(对不起发表第二个答案,但是把它放在上一个答案的评论中太长了)
你可以尝试@BeanParam所有的重复参数。 所以而不是每次只注入你的customBean注入它们,这将做的伎俩。
另一种更清洁的方法是可以注射
@Context UriInfo
要么
@Context ExtendedUriInfo
到你的资源类和非常方法,你可以简单地访问它们。 UriInfo更灵活,因为你的jvm将有一个较less的java源文件来pipe理,而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 }
您可以使用@Context UriInfo
来访问任何types的参数,而不是使用@PathParam
, @QueryParam
或其他参数。 所以你的代码可能是:
// 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保证是线程安全的,所以你可以保持资源类为单身。