如何在 Jersey 调用它之前获取与 URI 匹配的资源方法?

2022-01-21 00:00:00 java jersey jersey-1.0

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

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 URI so that I can scrape custom annotations from the method's parameters.

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

Based on this answer I should be able to inject ExtendedUriInfo and then use it to match the method:

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,一直到该方法被实际调用(此时我进行验证为时已晚).

But getMatchedMethod apparently returns null, all the way up until the method is actually invoked (at which point it's too late for me to do validation).

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

How can I retrieve the Method that will be matched to a given URI, before the resource method is invoked?

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

For those interested, I'm trying to roll my own required parameter validation, as described in JERSEY-351.

推荐答案

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

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.

这是我的解决方案,包括对所需查询参数的验证(使用 Guava 和 JSR 305):

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

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

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>

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

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

感谢这篇文章让我走上了正轨:如何在 Jersey ContainerResponseFilter 中获取资源注释

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

相关文章