使用Jersey / JAX-RS,资源类上的前缀匹配是否会阻止另一个资源上的子资源匹配?

时间:2017-02-24 04:04:26

标签: jersey jax-rs dropwizard

我正在尝试创建一个资源类来处理各种其他前缀下的类似功能,即“/ bar / {id} / foo”“/ fizz / {id} / foo”,因为将方法分组对我来说更有意义。

所以我有一个基本上是资源类:

@Path("/")
public class FooResource {
    @GET
    @Path("/foo")
    public String foo()
    {
        return "Hello Foo";
    }


    @GET
    @Path("/bar/stuff/foo")
    public String bar()
    {
        return ("Hello nested foo.");
    }
}

当它在Dropwizard应用程序中自行注册时,它可以正常工作(即按预期给出响应)。但是,如果使用公共前缀(如

)注册了另一个Resource类
@Path("/bar")
public class BarResource
{
    @GET
    @Path("/{id}")
    public String bar(@PathParam("id") String id)
    {
        return "Hello:" + id;
    }
}

然后FooResource中的子资源永远不会匹配导致404s。但在这两种情况下,Dropwizard都将FooResource SubResources列为找到的路径。

GET     /bar/stuff/foo (FooResource)
GET     /bar/{id} (BarResource)
GET     /foo (FooResource)

阅读JAX-RS规范的第3.7.2节,

  

用于RESTful Web服务的JSR-339 Java™API(“规范”)

     

版本:2.0

     

状态:最终版本

     

发布:2013年5月22日

这不是看起来就像它应该的方式一样。由于bar资源没有匹配/ bar / stuff / foo的子资源。我错过了一些关于它应该如何工作的东西吗?显然我可以重构我的代码,但我确实更喜欢这种组织方案。

4 个答案:

答案 0 :(得分:0)

因为我误解了你的问题,我再给它一个调试。我读了你指出的相关部分。首先,我将尝试概述应该发生什么,然后在球衣内发生的事情:

  1. 类级别的所有资源都会获得用于匹配路径的正则表达式,这对您来说意味着:
  2. @Path("/")变为(/.*)?

    @Path("bar")变为/bar(/.*)?

    如何在3.7.3中描述路径。我知道这些是正确的,因为我把它们从调试器中抓出来了:)

    这真的是我们需要看到这里发生的事情。现在我们有了正则表达式,JSR中描述的算法在3.7.2.1中规定了这种行为。(e):

      

    使用每个成员中的文字字符数作为排序E.   主键(降序),捕获组的数量为   二级密钥(降序)和捕获组的数量   使用非默认正则表达式(即不是'([/] +?)')作为   三级密钥(降序)

    字面字符是:

      

    这里,文字字符表示不是由模板产生的字符   变量替换。

    现在,查看PathMatchingRouter我们可以发现,在apply方法中,我们正在逐步完成可能的Routes并选择匹配的第一个/bar/stuff/foo。这也在这里描述为:2.7.2.1。(f)

      

    将Rmatch设置为E的第一个成员,并将U设置为值的值   Rmatch的最终捕获组与U匹配时。让C 0为   类Z的集合使得R(TZ)= Rmatch。根据定义,所有root   C 0中的资源类必须使用相同的URI路径进行注释   模板模数变量名称。

    问题是,正如你已正确指出的那样:

      

    根据定义,所有root   C 0中的资源类必须使用相同的URI路径进行注释   模板模数变量名称。

    我们通过使用两个不同的根路径来解决这个问题。这自然总是与第一个匹配,因此只能匹配一个。

    问题(从我这边):我们不是匹配错误的资源吗?从JSR,我相信匹配的资源应该相反地排序,每次都匹配usrun,因为根资源路径比另一个短。 (完全不确定)

    我尝试过使用调试器并取消匹配第一个(较短的)资源,这个资源有我指出的结果,每次都匹配另一个资源。

    但是,简而言之,这似乎是正确的(除了上述问题)

    泽西岛会发生什么(作为附加信息):

    泽西岛的资源模型将资源和方法分解为路由阶段。

    第1阶段 - >使用路径匹配所有路由

    Stage2 - >基于Stage1匹配所有与Stage1匹配的方法

    这意味着Stage1不是匹配的资源类,而只是一个高级路由阶段,作为路由子级,具有可能与根匹配的所有路径(这就是为什么它在类级别工作的原因)变量是一样的)。

    如果您希望自己调试(实际上这也很有趣),您可以查看这些内部类(应用中的断点):

    • MatchResultInitializerRouter
    • SubResourceLocatorRouter
    • MethodSelectingRouter
    • PathMatchingRouter

    我发现这也是附加信息:

    https://abhirockzz.wordpress.com/2015/03/02/quick-peek-at-jax-rs-request-to-method-matching/

    为了完整起见,这是有问题的JSR:

    http://download.oracle.com/otn-pub/jcp/jaxrs-2_0_rev_A-mrel-eval-spec/jsr339-jaxrs-2.0-final-spec.pdf

    我希望这会在一些细节上回答(有点自我回答)的问题。我再次对在开始时提出错误的问题表示歉意。

答案 1 :(得分:0)

我确实误解了规范中的算法(JaxRS规范的第3.7.2节)。算法部分的摘要显示我出错的地方是:

  1. 找到资源的最佳匹配(或匹配)。只考虑这些资源类。 "根据定义,C'中的所有根资源类必须使用相同的URI路径模板模数变量名称进行注释。"
  2. 如果你在C'中找不到匹配项然后抛出404。
  3. 我以为它会回到第1步并找到下一个最佳匹配。

    我认为最接近以这种方式组织我的代码的方法是为每个子路径分配一个单独的资源以获得所需的优先级。

答案 2 :(得分:0)

建立在你自己的答案和声明上:

  

看起来我最近能够组织我的代码   这种方式是为每个子路径获取一个单独的资源   所需的优先级。

我想添加子资源定位器作为完整性的选项。您可以这样组织代码:

public class Application extends io.dropwizard.Application<Configuration>{

    @Override
    public void run(Configuration configuration, Environment environment) throws Exception {
        environment.jersey().register(SubResourceTest.class);
    }

    public static void main(String[] args) throws Exception {
        new Application().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml");
    }

    public static class MyResourceBean {
        String test;
    }

    @Path("/bar")
    public static class SubResourceTest {

        @Path("bar")
        public SubR test() {
            return new SubR();
        }
    }

    @Path("")
    public static class SubR {

        @GET
        @Path("foo/stuff")
        public String fooStuff() {
            return "fooStuff";
        }

        @GET
        @Path("{id}")
        public String generic(@PathParam("id") String id) {
            return id;
        }

    }
}

现在SubResourceTest返回一个子资源,它为两个所需的路径提供服务。

或者,为了让它更加不稳定:),如果你绝对必须拆分资源类,这个资源示例也适用于我(不是这很漂亮,只是一点点玩):

@Path("/bar")
    public static class SubResourceTest {

        @Path("bar/{string}")
        public Object test(@PathParam("string") String s) {
            if(s.equals("foo")) {
                return new SubRStuff();
            }
            return new SubRGeneric(s);
        }
    }

    public static class SubRStuff {

        @GET
        @Path("stuff")
        public String fooStuff() {
            return "fooStuff";
        }

    }

    public static class SubRGeneric {

        private String id;

        public SubRGeneric(String id) {
            this.id = id;
        }

        @GET
        public String generic() {
            return id;
        }
    }

基本上,bar资源以编程方式检查路径(是foo?)然后返回处理问题的相应子资源。如果它是通用的,它只是传递String。这对我有用:

artur@pandaadb:~$ curl localhost:9085/api/bar/bar/HelloWorld
HelloWorld
artur@pandaadb:~$ curl localhost:9085/api/bar/bar/foo/stuff
fooStuff
curl localhost:9085/api/bar/bar/myNameIsArtur
myNameIsArtur

显然这可能不是你想要的,但它是另一种选择,也许它适合你的用例:)

答案 3 :(得分:0)

@Path("/")
public class FooResource {
    @GET
    @Path("/foo")
    public String foo()
    {
        return "Hello Foo";
    }


    @GET
    @Path("/bar/stuff/foo")
    public String bar()
    {
        return ("Hello nested foo.");
    }
}

@Path("/")
public class BarResource
{
    @GET
    @Path("/bar/{id}")
    public String bar(@PathParam("id") String id)
    {
        return "Hello:" + id;
    }
}

如果您这样更改代码,它将按我的尝试工作。