How to forward a REST request to another resource?

2019-04-08 15:52发布

问题:

In my current architecture, I have a JAX-RS resource that sits behind:

/categories
/categories/{catId}

that's implemented like this:

@Path("/categories")
@Produces("application/json")
public class CategoryResourcesApi {

    @GET
    public Response getCategories() {
        // ...
    }

    @GET @Path("/{catId}")
    public Response getCategory(@PathParam("catId") String catId) {
        // ...
    }

    // ...

}

and another one that serves:

/products
/products/{prodId}

and has a similar implementation:

@Path("/products")
@Produces("application/json")
public class ProductResourcesApi {

    @GET
    public Response getProducts() {
        // ...
    }

    // ...

}

Apart from these straightforward paths, I also need to serve these:

/categories/{catId}/products
/categories/{catId}/products/{prodId}

which would be products related to a specific category.

The most natural thing to do would be make ProductResourcesApi serve them, but by the way I understand the JAX-RS annotations structure, this can only be served by CategoryResourcesApi (or eventually by a third class, I think).

I'm using @Context and other annotations in my resource implementations, so a direct new ProductResourcesAPI().getProducts() wouldn't work, I guess.

Is there any way to forward from one resource path to another within the JAX-RS (or Jersey) framework? What other options do I have? I'd like to keep all this easily maintainable if possible, that's why I chose one resource for each root path with subresources within.

回答1:

For this you can use Sub-resource locators, which is basically a method in the resource class that returns another resource class. The thing about the examples in the link is that they instantiate the resource class themselves, for example

@Path("/item")
public class ItemResource {
    @Path("content")
    public ItemContentResource getItemContentResource() {
        return new ItemContentResource();
    }
}

public class ItemContentResource {
    @PUT
    @Path("{version}")
    public void put(@PathParam("version") int version)
    }
}

which works, but I am not sure if it preserves injections, for instance if you wanted to inject @Context UriInfo into a field in ItemContentResource. It should work though if you injected into the method param instead.

To get around this, there is the ResourceContext, which when used, should preserve all the injections. For example in your current case, you can do

@Path("/categories")
@Produces("application/json")
public static class CategoryResourcesApi {

    @Context
    private ResourceContext resourceContext;

    @Path("/{catId}/products")
    public ProductResourcesApi getProducts() {
        return resourceContext.getResource(ProductResourcesApi.class);
    }
}

@Path("/products")
@Produces("application/json")
public static class ProductResourcesApi {

    @Context
    private UriInfo info;

    @GET
    @Path("/{id}")
    public Response getProducts(
            @PathParam("id") String prodId,
            @PathParam("catId") String catId) {
    }
}

The getProducts would map to the URI /categories/{catId}/products/{prodId}. You just need to check if the catId is null (only if you need it to do any lookup) I guess to determine if the request is a request to the root products resource or to the parent categories resource. Small price to pay for code reuse I guess.

And just looking at your comment, I believe in the past Swagger didn't support sub-resource locators, but I believe now they do. You may want to search around for any discussions if you have problems with it. Here's a discussion, and another one, and another one