web-dev-qa-db-ja.com

Jerseyが呼び出す前にURIに一致するリソースメソッドを取得する方法

リクエストのパラメーターのカスタム検証を行う ContainerRequestFilter を実装しようとしています。メソッドのパラメーターからカスタムアノテーションを取得できるように、URIに一致するリソースメソッドを検索する必要があります。

この答え に基づいて 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 で説明されているように、自分の必須パラメーター検証をロールしようとしています。

20
Paul Bellora

ジャージーだけで問題を解決する方法を見つけました。少なくとも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;
        }
    }
}

そして、ResourceFilterFactoryweb.xmlのサーブレットの初期パラメータとしてジャージーに登録されています。

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

起動時に、ValidationFilterFactory.createは、ジャージーによって検出された各リソースメソッドに対して呼び出されます。

クレジットは私を正しい軌道に乗せるためにこの投稿に行きます: Jersey ContainerResponseFilterでリソースアノテーションを取得するにはどうすればよいですか

13
Paul Bellora

実際には、カスタムリクエストフィルターにResourceInfoを挿入する必要があります。 RESTEasyで試してみましたが、そこで機能します。利点は、Jersey実装ではなく、JSRインターフェースに対してコーディングすることです。

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

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

私はあなたがジャージーのみのソリューションを探していることを知っていますが、これは物事を機能させるはずの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;
  }

}
3
condit

Resteasy-jaxrs-3.0.5では、ResourceMethodInvoker内のContainerRequestContext.getProperty()から、一致するリソースメソッドを表す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
dthorpe