Jersey/Jax-RS:如何过滤资源和子资源

5
在Jersey 2中,我如何将过滤器绑定到资源的所有方法以及其子资源的所有方法?
例如,如果我有以下两个资源:
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.server.model.Resource;

@Path("/myresource/{id: \\d+}")
@Produces(MediaType.APPLICATION_JSON)
@Singleton
class RootResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response get(@PathParam("id") Long id) {
        return Response.ok().build();
    }

    @Path("/sub")
    public Resource getSubResource() {
        return Resource.from(SubResource.class);
    }
}

@Produces(MediaType.APPLICATION_JSON)
@Singleton
class SubResource {
    @GET
    @Path("/{subid: \\d+}")
    public Response get(@PathParam("id") Long id, @PathParam("subid") Long subid) {
        return Response.ok().build();
    }
}

我希望能过滤RootResource.get(Long)SubResource.get(Long, Long),但如果有其他资源,则不应该被过滤。
使用DynamicFeature,我们只能获取类和方法的信息。
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.FeatureContext;

public class MyFeature implements DynamicFeature {

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        // Here how can I find out that SubResource is actually a sub-resource of RootResource
    }

}

我的想法是,我想过滤掉所有针对一组特定id的调用(这组id是动态的),类似于以下内容:

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;


public class MyFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        for(Object resource:requestContext.getUriInfo().getMatchedResources()) {
            if(resource instanceof RootResource) {
                Long id = Long.valueOf(requestContext.getUriInfo().getPathParameters().getFirst("id"));
                // ...
            }
        }
    }

}

但我希望避免不得不搜索匹配的资源。这可行吗?
2个回答

6
我不完全确定我理解了问题,但是它似乎是你希望限制哪些资源应该经过过滤器。为此,你可以简单地使用名称绑定
基本步骤:
  1. Create a @NameBinding annotation

    @NameBinding 
    @Target({METHOD, TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Filtered {    
    }
    
  2. Annotate the filter

    @Filtered
    @Provider
    public class MyFilter implements ContainerRequestFilter {
    
  3. Annotate whatever root resources, resource methods, sub resource classes you want to be filtered


更新

好的,经过一些尝试,我找到了几个解决方案...虽然都不太好看,但是能完成任务。

请记住,在DynamicFeature中的configure对于每个资源(方法)都会被调用。

算法1:

  1. Get the method being checked and get its declaring class (in the case of a method in a sub resource, the declaring class will be the sub resource class)

    Class<?> possibleSubResource =
             resourceInfo.getResourceMethod().getDeclaringClass();
    
  2. Build a temporary Resource from your root resource

    Resource resource = Resource.from(SomeResource.class);
    
  3. Iterate its child resources, checking if it's a resource locator

    for (Resource childResource : resource.getChildResources()) {
        if (childResource.getResourceLocator() != null) {
    
  4. If is is resource locator get the return type.

    ResourceMethod sub = childResource.getResourceLocator();
    Class responseClass = sub.getInvocable().getRawResponseType();
    
  5. Then check if the response type from step 4 == the declaring class from step 1.

    if (responseClass == possibleSubResource) {
        context.register(SomeFilter.class);
    }
    
为了使上述内容生效,实际上您需要从定位器方法中返回子资源类型,而不是Resource。(您可以尝试使用Resource来使其生效,但我还没有想出来)
@Path("{id}")
public SomeSubResource getSubResource() {
    return new SomeSubResource();
}

以下是完整的代码,已经可运行(未经过实战测试 :-)

@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
    Class<?> resourceClass = resourceInfo.getResourceClass();

    if (resourceClass == SomeResource.class) {
        context.register(SomeFilter.class);
    }

    Class<?> possibleSubResource = resourceInfo.getResourceMethod().getDeclaringClass();

    Resource resource = Resource.from(SomeResource.class);
    for (Resource childResource : resource.getChildResources()) {
        if (childResource.getResourceLocator() != null) {
            ResourceMethod sub = childResource.getResourceLocator();
            Class responseClass = sub.getInvocable().getRawResponseType();

            if (responseClass == possibleSubResource) {
                context.register(SomeFilter.class);
            }
        }
    }
}

算法2:

为了使其正常工作,我们建立的假设是:一个子资源的定义需要使用@Path进行注释,并且没有任何Http方法的注释。

  1. Get the method being checked and get its declaring class (in the case of a method in a sub resource, the declaring class will be the sub resource class)

    Class<?> possibleSubResource =
             resourceInfo.getResourceMethod().getDeclaringClass();
    
  2. Iterate through the Methods in the root resource class

    for (Method method : SomeResource.class.getDeclaredMethods()) {
    
  3. Check if the method has an Http method annotation

    boolean isHttpPresent = false;
    for (Class annot : Arrays.asList(GET.class, POST.class, PUT.class, DELETE.class)) {
        if (method.isAnnotationPresent(annot)) {
            isHttpPresent = true;
            break;
        }
    }
    
  4. Check if the method has the @Path annotation. If it does, and it has not Http method annotations, then we register the filter

    if (method.isAnnotationPresent(Path.class) && !isHttpPresent) {
        Class subResourceClass = method.getReturnType();
        if (subResourceClass == possibleSubResource) {
            context.register(SomeFilter.class);
        }
    }
    

以下是完整的代码:

@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
    Class<?> resourceClass = resourceInfo.getResourceClass();

    if (resourceClass == SomeResource.class) {
        context.register(SomeFilter.class);
    }

    Class<?> possibleSubResource = resourceInfo.getResourceMethod().getDeclaringClass();

    for (Method method : SomeResource.class.getDeclaredMethods()) {
        boolean isHttpPresent = false;
        for(Class annot : Arrays.asList(GET.class,POST.class,PUT.class, DELETE.class)){
            if (method.isAnnotationPresent(annot)) {
                isHttpPresent = true;
                break;
            }
        }
        if(method.isAnnotationPresent(Path.class) && !isHttpPresent){
            Class subResourceClass = method.getReturnType();
            if (subResourceClass == possibleSubResource) {
                context.register(SomeFilter.class);
            }
        }
    }
}

再次强调,这些解决方案都没有经过实战验证,但是对我尝试过的少数情况有效。个人认为,只需使用名称绑定即可,但也许您可以向Jersey团队提出此问题。这(自动注册子资源时,根资源已被注册)似乎应该可以直接工作,或者至少可以进行配置。


我也考虑过这个,但我想避免这种情况。如果明天我添加了一个新的方法和/或新的子资源,我不想知道我还需要添加那个注释。我希望这尽可能地通用,以避免任何未来的错误。但还是谢谢你的回答。 - Guillaume Polet
看我的更新。我不喜欢它,但它可能会给你一些想法。 - Paul Samsotha

2

我有类似的需求:我想要一个注解来专门过滤资源方法,以实现类似于这样的功能:

@Path("/api/sample")
@Produces(MediaType.APPLICATION_JSON)
public class SampleResource {

    @Path("/filtered")
    @GET
    @Sample(value = "a sample value")
    public Hello filtered() {
        return new Hello("filtered hello");
    }

    @Path("/nonfiltered")
    @GET
    public Hello raw() {
        return new Hello("raw hello");
    }
}

我的注释如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sample {

    String value() default "";
}

我最终使用了一个DynamicFeature来在资源上注册一个Filter
@Provider
public class SampleFeature implements DynamicFeature {

    private SampleFilter sampleFilter;

    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        if (resourceInfo.getResourceMethod().getAnnotation(Sample.class) != null) {
            if (sampleFilter == null) {
                this.sampleFilter = new SampleFilter();
            }
            context.register(sampleFilter);
        }
    }
}

有一个棘手的问题是如何在我的过滤器中获取注释值,因此需要了解ExtendedUriInfo,请参见下文:

public class SampleFilter implements ContainerRequestFilter {

    public SampleFilter() {
    }

    public void filter(ContainerRequestContext containerRequestContext) throws IOException {
        String sampleValue = this.getAnnotation(containerRequestContext).value();
        // do some filtering based on the Sample Value
        }

    private Sample getAnnotation(ContainerRequestContext requestContext) {
        ResourceMethod method = ((ExtendedUriInfo) (requestContext.getUriInfo()))
                .getMatchedResourceMethod();
        Method invokedMethod = method.getInvocable().getHandlingMethod();
        return invokedMethod.getAnnotation(Sample.class);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接