How to get resource method matched to URI before Jersey invokes it?

后端 未结 4 1240
感动是毒
感动是毒 2020-12-08 15:35

I\'m trying to implement a ContainerRequestFilter that does custom validation of a request\'s parameters. I need to look up the resource method that will be matched to the U

相关标签:
4条回答
  • 2020-12-08 16:08

    In resteasy-jaxrs-3.0.5, you can retrieve a ResourceMethodInvoker representing the matched resource method from ContainerRequestContext.getProperty() inside a ContainerRequestFilter:

       import org.jboss.resteasy.core.ResourceMethodInvoker;
    
       public class MyRequestFilter implements ContainerRequestFilter
       {
           public void filter(ContainerRequestContext request) throws IOException
           {
                String propName = "org.jboss.resteasy.core.ResourceMethodInvoker";
                ResourceMethodInvoker invoker = (ResourceMethodInvoker)request.getProperty();
                invoker.getMethod().getParameterTypes()....
           }
       }
    
    0 讨论(0)
  • 2020-12-08 16:18

    I know you're looking for a Jersey only solution but here's a Guice approach that should get things working:

    public class Config extends GuiceServletContextListener {
    
      @Override
      protected Injector getInjector() {
        return Guice.createInjector(
            new JerseyServletModule() {
              @Override
              protected void configureServlets() {
                bindInterceptor(Matchers.inSubpackage("org.example"), Matchers.any(), new ValidationInterceptor());
                bind(Service.class);
    
                Map<String, String> params = Maps.newHashMap();
                params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.example");
                serve("/*").with(GuiceContainer.class, params);
              }
            });
      }
    
      public static class ValidationInterceptor implements MethodInterceptor {    
        public Object invoke(MethodInvocation method) throws Throwable {
          System.out.println("Validating: " + method.getMethod());
          return method.proceed();
        }
      }
    
    }
    
    @Path("/")
    public class Service {
    
      @GET
      @Path("service")
      @Produces({MediaType.TEXT_PLAIN})
      public String service(@QueryParam("name") String name) {
        return "Service " + name;
      }
    
    }
    

    EDIT: A performance comparison:

    public class AopPerformanceTest {
    
      @Test
      public void testAopPerformance() {
        Service service = Guice.createInjector(
            new AbstractModule() {
              @Override
              protected void configure() { bindInterceptor(Matchers.inSubpackage("org.example"), Matchers.any(), new ValidationInterceptor()); }
            }).getInstance(Service.class);
        System.out.println("Total time with AOP: " + timeService(service) + "ns");
      }
    
      @Test
      public void testNonAopPerformance() {
        System.out.println("Total time without AOP: " + timeService(new Service()) + "ns");
      }
    
      public long timeService(Service service) {
        long sum = 0L;
        long iterations = 1000000L;
        for (int i = 0; i < iterations; i++) {
          long start = System.nanoTime();
          service.service(null);
          sum += (System.nanoTime() - start);
        }
        return sum / iterations;
      }
    
    }
    
    0 讨论(0)
  • 2020-12-08 16:23

    I figured out how to solve my problem using only Jersey. There's apparently no way to match a request's URI to the method that will be matched before that method is invoked, at least in Jersey 1.x. However, I was able to use a ResourceFilterFactory to create a ResourceFilter for each individual resource method - that way these filters can know about the destination method ahead of time.

    Here's my solution, including the validation for required query params (uses Guava and JSR 305):

    public final class ValidationFilterFactory implements ResourceFilterFactory {
    
        @Override
        public List<ResourceFilter> create(AbstractMethod abstractMethod) {
    
            //keep track of required query param names
            final ImmutableSet.Builder<String> requiredQueryParamsBuilder =
                    ImmutableSet.builder();
    
            //get the list of params from the resource method
            final ImmutableList<Parameter> params =
                    Invokable.from(abstractMethod.getMethod()).getParameters();
    
            for (Parameter param : params) {
                //if the param isn't marked as @Nullable,
                if (!param.isAnnotationPresent(Nullable.class)) {
                    //try getting the @QueryParam value
                    @Nullable final QueryParam queryParam =
                            param.getAnnotation(QueryParam.class);
                    //if it's present, add its value to the set
                    if (queryParam != null) {
                        requiredQueryParamsBuilder.add(queryParam.value());
                    }
                }
            }
    
            //return the new validation filter for this resource method
            return Collections.<ResourceFilter>singletonList(
                    new ValidationFilter(requiredQueryParamsBuilder.build())
            );
        }
    
        private static final class ValidationFilter implements ResourceFilter {
    
            final ImmutableSet<String> requiredQueryParams;
    
            private ValidationFilter(ImmutableSet<String> requiredQueryParams) {
                this.requiredQueryParams = requiredQueryParams;
            }
    
            @Override
            public ContainerRequestFilter getRequestFilter() {
                return new ContainerRequestFilter() {
                    @Override
                    public ContainerRequest filter(ContainerRequest request) {
    
                        final Collection<String> missingRequiredParams =
                                Sets.difference(
                                        requiredQueryParams,
                                        request.getQueryParameters().keySet()
                                );
    
                        if (!missingRequiredParams.isEmpty()) {
    
                            final String message =
                                    "Required query params missing: " +
                                    Joiner.on(", ").join(missingRequiredParams);
    
                            final Response response = Response
                                    .status(Status.BAD_REQUEST)
                                    .entity(message)
                                    .build();
    
                            throw new WebApplicationException(response);
                        }
    
                        return request;
                    }
                };
            }
    
            @Override
            public ContainerResponseFilter getResponseFilter() {
                return null;
            }
        }
    }
    

    And the ResourceFilterFactory is registered with Jersey as an init param of the servlet in web.xml:

    <init-param>
        <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
        <param-value>my.package.name.ValidationFilterFactory</param-value>
    </init-param>
    

    At startup, ValidationFilterFactory.create gets called for each resource method detected by Jersey.

    Credit goes to this post for getting me on the right track: How can I get resource annotations in a Jersey ContainerResponseFilter

    0 讨论(0)
  • 2020-12-08 16:27

    Actually, you should try to inject ResourceInfo into your custom request filter. I have tried it with RESTEasy and it works there. The advantage is that you code against the JSR interfaces and not the Jersey implementation.

    public class MyFilter implements ContainerRequestFilter
    {
        @Context
        private ResourceInfo resourceInfo;
    
        @Override
        public void filter(ContainerRequestContext requestContext)
                throws IOException
        {
            Method theMethod = resourceInfo.getResourceMethod();
            return;
        }
    }
    
    0 讨论(0)
提交回复
热议问题