在这个例子中,使用子资源的PUT请求是如何由JAX-RS运行时处理的?

bra*_*orm 4 jax-rs put jersey java-ee

我正在阅读oracle docs中提供的Jersey Sample中的存储服务示例示例.

我只是无法理解JAX-RS运行时如何解决这个PUT请求?

curl -X PUT http://127.0.0.1:9998/storage/containers/quotes
Run Code Online (Sandbox Code Playgroud)

这是与此请求对应的代码段(取自上面的链接).

@Path("/containers")
@Produces("application/xml")
public class ContainersResource {
    @Context UriInfo uriInfo;
    @Context Request request;

    @Path("{container}")
    public ContainerResource getContainerResource(@PathParam("container") String container) {
        return new ContainerResource(uriInfo, request, container);
    }

    @GET
    public Containers getContainers() {
        System.out.println("GET CONTAINERS");

        return MemoryStore.MS.getContainers();
    }    
}
Run Code Online (Sandbox Code Playgroud)

但是你可以注意到,没有办法@PUT annotation.但是需要这种getContainerResource方法/containers/{container}.在此方法中,ContainerResource返回一个新实例.我不确定如何处理上述PUT请求.请解释.

这是ContainerResource class:

@Produces("application/xml")
public class ContainerResource {
    @Context UriInfo uriInfo;
    @Context Request request;
    String container;

    ContainerResource(UriInfo uriInfo, Request request, String container) {
        this.uriInfo = uriInfo;
        this.request = request;
        this.container = container;
    }

    @GET
    public Container getContainer(@QueryParam("search") String search) {
        System.out.println("GET CONTAINER " + container + ", search = " + search);

        Container c = MemoryStore.MS.getContainer(container);
        if (c == null)
            throw new NotFoundException("Container not found");


        if (search != null) {
            c = c.clone();
            Iterator<Item> i = c.getItem().iterator();
            byte[] searchBytes = search.getBytes();
            while (i.hasNext()) {
                if (!match(searchBytes, container, i.next().getName()))
                    i.remove();
            }
        }

        return c;
    }    

    @PUT
    public Response putContainer() {
        System.out.println("PUT CONTAINER " + container);

        URI uri =  uriInfo.getAbsolutePath();
        Container c = new Container(container, uri.toString());

        Response r;
        if (!MemoryStore.MS.hasContainer(c)) {
            r = Response.created(uri).build();
        } else {
            r = Response.noContent().build();
        }

        MemoryStore.MS.createContainer(c);
        return r;
    }

    @DELETE
    public void deleteContainer() {
        System.out.println("DELETE CONTAINER " + container);

        Container c = MemoryStore.MS.deleteContainer(container);
        if (c == null)
            throw new NotFoundException("Container not found");
    } 


    @Path("{item: .+}")
    public ItemResource getItemResource(@PathParam("item") String item) {
        return new ItemResource(uriInfo, request, container, item);
    }

    private boolean match(byte[] search, String container, String item) {
        byte[] b = MemoryStore.MS.getItemData(container, item);

        OUTER: for (int i = 0; i < b.length - search.length + 1; i++) {
            for (int j = 0; j < search.length; j++) {
                if (b[i + j] != search[j])
                    continue OUTER;
            }

            return true;
        }

        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*man 5

这是一个称为子资源定位器的文档功能:https: //jersey.java.net/documentation/latest/jaxrs-resources.html

@Path("{container}")
public ContainerResource getContainerResource(@PathParam("container") String container) {
    return new ContainerResource(uriInfo, request, container);
}
Run Code Online (Sandbox Code Playgroud)

上面的@Path注释将ContainerResource标识为子资源.请注意,ContainerResource确实具有正在调用的带注释的PUT方法.如果您需要进一步解释,我可以尝试扩展此答案.

更新

这不容易解释,但这里有一个解释它...

让我们通过查看您的类支持的各种URL开始解释这一点.ContainersResource类实现的唯一端点如下:

GET /containers/
Run Code Online (Sandbox Code Playgroud)

此端点是顶级资源的一部分.显然它返回容器的集合/列表.

现在如果我们想要一个端点来获取id的特定容器呢?正常的REST端点会在集合上使用GET操作公开端点,然后将id作为路径参数(PathParam),因此对于ID为27的容器,调用可能如下所示:

GET /containers/27/
Run Code Online (Sandbox Code Playgroud)

这样做的一个解决方案如下:

@Path("/containers")
@Produces("application/xml")
public class ContainersResource {
    @Context UriInfo uriInfo;
    @Context Request request;

    @GET
    public Containers getContainers() {
        System.out.println("GET CONTAINERS");

        return MemoryStore.MS.getContainers();
    }

    //
    // This is a solution WITHOUT sub-resource...
    // Note that the Path annotation is same as you have it, but
    // now the HTTP method annotation is provided.  Also, the
    // method returns Container instead of ContainerResource
    //
    @Path("{container}")
    @GET
    public Container getContainerResource(@PathParam("container") String container) {
        // Go to memory store and get specific container...
        Container x = findContainer(container);
        return x;
    }

    //
    // Again, without sub-resource, we can define PUT method
    // on specific container id and again define the path param
    //
    @Path("{container}")
    @PUT
    public Response putContainer(@PathParam("container") String container) {
        // Process payload to build container, put into memory store
        Response r = putContainer(container, ...);
        return r;
    }

}
Run Code Online (Sandbox Code Playgroud)

不使用子资源导致我们必须将多个GET,PUT,POST,DELETE方法放入同一个类中,因为我们将方法从较低级别的资源提升到最顶层的资源类.它还使我们必须多次为容器ID定义路径参数.这只是两个级别的资源(容器集合和特定容器)所以它看起来并不太糟糕,但随着我们的路径变得更深入呢?容器具有项目,因此完全可访问的API可以实现端点,该端点允许您仅检索特定集合中的项目,甚至是集合中的特定项目.这些调用分别如下所示:

GET /containers/27/items/ (to get the items collection)
GET /containers/27/items/9/ (to get specific item from collection)
Run Code Online (Sandbox Code Playgroud)

所以现在,我们的父资源需要定义4个单独的GET方法.

为了避免在同一个类中使用多个GET/POST/PUT/DELETE方法,我们仍然可以将这些方法分解为4个不同的类,每个类在类上都有唯一的Path注释,但是如果路径中的某些术语(如"容器")需要重命名,那么我们将不得不在4个地方而不是一个地方更新代码.此外,必须在每个方法上定义沿路径的所有路径参数.

为了说明这一点,请考虑您提供的使用子资源的ContainersResource类:

@Path("/containers")
@Produces("application/xml")
public class ContainersResource {
    @Context UriInfo uriInfo;
    @Context Request request;

    @Path("{container}")
    public ContainerResource getContainerResource(@PathParam("container") String container) {
        return new ContainerResource(uriInfo, request, container);
    }

}
Run Code Online (Sandbox Code Playgroud)

getContainerResource方法声明存在由path参数"container"标识的子资源.如果我们不使用子资源,可以直接在ContainerResource类中实现,如下所示:

@Path("containers/{container}")
@Produces("application/xml")
public class ContainerResource {

    @GET
    public Container getContainer(
    @PathParam("container") String container,
    @QueryParam("search") String search) {
        System.out.println("GET CONTAINER " + container + ", search = " + search);

        Container c = MemoryStore.MS.getContainer(container);
    // do work
        return c;
    }    
}
Run Code Online (Sandbox Code Playgroud)

请注意,我必须将Path注释添加到类中以定义端点位置以及存在路径参数的事实.另外,我必须向GET方法添加一个PathParam参数(并且必须将它添加到我的其他方法中)以了解容器值是什么.

为了进一步证明这一点,请考虑我们是否在不使用子资源的情况下实现ContainerItemsResource:

@Path("containers/{container}/items")
@Produces("application/xml")
public class ContainerItemsResource {

    @GET
    public ContainerItems getContainerItems(
    @PathParam("container") String container) {
        Container c = MemoryStore.MS.getContainer(container);
        return c.getItems();
    }    
}
Run Code Online (Sandbox Code Playgroud)

和ContainerItemResource

@Path("containers/{container}/items/{item}")
@Produces("application/xml")
public class ContainerItemResource {

    @GET
    public ContainerItem getContainerItem(
    @PathParam("container") String container,
        @PathParam("item" String item) {
        Container c = MemoryStore.MS.getContainer(container);
        return c.getItems();
    }    
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们再次重复完整的Path并且必须在每个方法上重新定义容器路径参数.

子资源提供了一种优雅的解决方案:(1)允许路径中的每个级别使用单个GET/PUT/POST/DELETE方法拥有自己的类,(2)不需要在多个位置重新定义路径中的级别,并且( 3)不需要在每个方法上重新定义查询参数.以下是使用子资源方法的四个资源文件(仅提供GET方法用于说明):

@Path("/containers")
@Produces("application/xml")
public class ContainersResource {
    @Context UriInfo uriInfo;
    @Context Request request;

    // Define sub-resource for specific container
    @Path("{container}")
    public ContainerResource getContainerResource(@PathParam("container") String container) {
        return new ContainerResource(container);
    }

    // Provide @GET, @PUT, @POST, @DELETE to get collection of containers

    @GET
    public Containers getContainers() {
        return MemoryStore.MS.getContainers();
    }
}


@Produces("application/xml")
public class ContainerResource {
    @Context UriInfo uriInfo;
    @Context Request request;
    String container;

    // Constructor allowing it to be used as sub-resource
    ContainerResource(String container) {
        this.container = container;
    }

    // Define sub-resource for items collection
    @Path("items")
    public ContainerItemsResource getContainerItemsResource() {
        return new ContainerItemsResource(container);
    }

    // Provide @GET, @PUT, @POST, @DELETE to get specific container

    // Notice that path params are not redefined...
    @GET
    public Container getContainer() {
        Container c = MemoryStore.MS.getContainer(container);
        return c;
    }    

}


@Produces("application/xml")
public class ContainerItemsResource {
    @Context UriInfo uriInfo;
    @Context Request request;
    String container;

    // Constructor allowing it to be used as sub-resource
    ContainerItemsResource(String container) {
        this.container = container;
    }

    // Define sub-resource for specific item
    @Path("{item}")
    public ContainerItemsResource getContainerItemsResource(@PathParam("container") String container, @PathParam("item") String item) {
        return new ContainerItemResource(container, item);
    }

    // Provide @GET, @PUT, @POST, @DELETE to get specific container items collection

    // Notice that path params are not redefined...
    @GET
    public ContainerItems getContainerItems() {
        Container c = MemoryStore.MS.getContainer(container);
        return c.getItems();
    }    

}


@Produces("application/xml")
public class ContainerItemResource {
    @Context UriInfo uriInfo;
    @Context Request request;
    String container;
    String item;

    // Constructor allowing it to be used as sub-resource
    ContainerItemResource(String container, String item) {
        this.container = container;
        this.item = item;
    }

    // Provide @GET, @PUT, @POST, @DELETE to get specific container item

    // Notice that path params are not redefined...
    @GET
    public ContainerItem getContainerItem() {
        Container c = MemoryStore.MS.getContainer(container);
        return c.getItem(item);
    }    

}
Run Code Online (Sandbox Code Playgroud)

通过使用子资源提供这个四级深度资源的示例,希望它能澄清您的代码正在做什么.子资源方法在整个资源中消除了重复的路径和路径参数定义,使代码更易于维护,并且(意见)更易于阅读.