【问题标题】:How to get resource method matched to URI before Jersey invokes it?如何在 Jersey 调用它之前获取与 URI 匹配的资源方法?
【发布时间】:2013-04-24 14:20:47
【问题描述】:

我正在尝试实现一个ContainerRequestFilter,它对请求的参数进行自定义验证。我需要查找将与 URI 匹配的资源方法,以便我可以从方法的参数中抓取自定义注释。

基于this answer我应该可以注入ExtendedUriInfo然后用它来匹配方法:

public final class MyRequestFilter implements ContainerRequestFilter {

    @Context private ExtendedUriInfo uriInfo;

    @Override
    public ContainerRequest filter(ContainerRequest containerRequest) {

        System.out.println(uriInfo.getMatchedMethod());

        return containerRequest;
    }
}

但是getMatchedMethod 显然返回null,一直到该方法被实际调用(此时我进行验证为时已晚)。

如何在调用资源方法之前检索将与给定 URI 匹配的 Method


对于那些感兴趣的人,我正在尝试推出自己的必需参数验证,如JERSEY-351 中所述。

【问题讨论】:

  • 这必须是仅适用于 Jersey 的解决方案,还是您会考虑使用某种 AOP?
  • @condit 我目前正在探索将 AOP 与 Guice 结合使用。我可以“接受”任何解决方案,但我想将赏金奖励给仅限泽西岛的人,即使它是骇人听闻的。
  • 您在寻找 Jersey 1.x 或 2.x 解决方案吗?
  • @condit 我仅限于最新的 Jersey 1.x,特别是 1.17。如果我理解正确,2.x 还没有准备好生产(尤其是因为 JAX-RS 2.0 尚未最终确定)。出于好奇,您知道 Jersey 2.0 是否会/是否会进行我正在寻找的那种参数验证?
  • 是的 - 2.0 尚未准备好生产,但 JAX-RS 2.0 应该支持标准 bean 验证。

标签: java jersey jersey-1.0


【解决方案1】:

实际上,您应该尝试将ResourceInfo 注入您的自定义请求过滤器。我已经用 RESTEasy 试过了,它在那里工作。优点是您针对 JSR 接口而不是 Jersey 实现进行编码。

public class MyFilter implements ContainerRequestFilter
{
    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext)
            throws IOException
    {
        Method theMethod = resourceInfo.getResourceMethod();
        return;
    }
}

【讨论】:

  • ResourceInfo 类是 jax-rs 2.0 的一部分。以上内容不适用于 Jersey 1.x
  • 我在我的 AuthenticationFilter(它实现了 ContainerRequestFilter)中尝试了这个,但我不断收到 null 用于 getResourceMethod()。这是否仅适用于非 PreMatching 提供者?
  • 对我的情况很好。我需要检查资源方法中是否存在特定注释。我用这个:注解securityCheckedAnnotation = resourceInfo.getResourceMethod().getDeclaredAnnotation(SecurityChecked.class);
【解决方案2】:

在 resteasy-jaxrs-3.0.5 中,您可以从 ContainerRequestContext.getProperty() 中的 ContainerRequestFilter 中检索代表匹配资源方法的 ResourceMethodInvoker

   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()....
       }
   }

【讨论】:

    【解决方案3】:

    我想出了如何仅使用泽西岛来解决我的问题。至少在 Jersey 1.x 中,显然没有办法将请求的 URI 与在调用该方法之前将匹配的方法相匹配。但是,我可以使用ResourceFilterFactory 为每个单独的资源方法创建一个ResourceFilter - 这样这些过滤器就可以提前知道目标方法。

    这是我的解决方案,包括对所需查询参数的验证(使用 Guava 和 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;
            }
        }
    }
    

    ResourceFilterFactory 已在 Jersey 注册为 web.xml 中 servlet 的 init 参数:

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

    在启动时,Jersey 检测到的每个资源方法都会调用 ValidationFilterFactory.create

    感谢这篇文章让我走上了正轨:How can I get resource annotations in a Jersey ContainerResponseFilter

    【讨论】:

    【解决方案4】:

    我知道您正在寻找仅适用于泽西岛的解决方案,但这里有一种 Guice 方法可以让事情正常进行:

    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;
      }
    
    }
    

    编辑:性能比较:

    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;
      }
    
    }
    

    【讨论】:

    • 嗯,我似乎在使用 Guice AOP 时遇到了一些严重的性能损失。您是否知道这是预期的多少以及是否有 Guice 文档解决了这个问题?我的搜索没有找到很多特定于 Guice AOP 性能的结果。
    • 我不知道这样的问题。我在答案中添加了性能测试。在我的本地主机上,我得到:Total time with AOP: 109ns Total time without AOP: 31ns。您确定不是拦截器中运行的代码导致了问题吗?
    • 我排除了拦截器——它实际上是被拦截方法的body,在拦截时运行速度较慢。我只看到该方法体的平均大约 600 毫秒,其中大约 90 毫秒是正常的。但是,我尝试了一种不同的资源方法,该方法基本上是空的,并且没有发现显着的额外成本。我认为关键的区别可能是我选择测试的资源方法是调用同一类中的其他方法。只是猜测,但由于 Guice AOP 通过动态子类化工作,这可能是其中的一部分。没有时间进一步试验。
    • 您可以尝试更改bindInterceptor(Matchers.inSubpackage("org.example"), Matchers.any(), new ValidationInterceptor()); 以匹配您提供的注释,而不是匹配包中的所有方法。如果所有方法都在同一个包中,它们可能会因验证代码而变慢。
    • 这实际上是我已经在做的事情:(我将尝试在资源方法中内联所有内容,看看我的理论是否成立——我会让你知道它是怎么回事。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-01
    • 2017-06-28
    • 1970-01-01
    相关资源
    最近更新 更多