我如何扩展 Jersey 的参数注释? 附注

问题描述

据我所知,Jersey 不支持深对象参数(?type[n1]=v1&type[n2]=v2 形式的参数)。

是否可以将其添加为扩展名?如果是这样,如何?

我的想法是有一个类似于 @QueryParam 的注释,比如说 @DeepObjectParam,我会用它来注释这样的字段:

   @GET
   public Response(@DeepObjectParam("type") Map<String,String> type) {
       // ...
   }

让泽西岛注入地图。

解决方法

根据您使用的 Jersey 版本,您需要实现的接口会有所不同。在 Jersey 2.0-2.25.1 中,类是 ValueFactoryProvider,而 2.26+ 是 ValueParamProvider

对于这两个类,实现将是相似的。有一种方法可以实现,它采用 Parameter 参数。我们使用此 Parameter 来检查此提供程序是否能够处理此类参数。如果检查通过,则该方法应返回提供实际参数的 FactoryFunction(取决于版本)。如果检查失败,则应返回 null。

比如参数用@DeepObjectParam注解,参数类型是Map,那么我们就应该检查这两件事。

@Override
public Function<ContainerRequest,?> getValueProvider(Parameter param) {

    if (param.isAnnotationPresent(DeepObjectParam.class) && isStringStringMap(param)) {
        return new DeepParamFunction(param);
    }
    return null;
}

这里,DeepParamFunction 是一个 Function,它接受​​单个 ContainerRequest 参数。它将解析查询参数,然后返回 Map

在您实现所需的类后,您需要在 Jersey 中注册它。同样,根据您使用的 Jersey 版本,注册会有所不同(但相似)。在这两种情况下,您都需要使用 AbstractBinder

注册一个 ResourceConfig
register(new AbstractBinder() {
    @Override
    protected void configure() {
        bind(DeepObjectParamProvider.class)
                // 2.0-2.25.1 you will use ValueFactoryProvider.class
                .to(ValueParamProvider.class)
                .in(Singleton.class);
    }
});

对于 Jersey 的两个版本,您将使用相同的 AbstractBinder 类,但导入会有所不同。在 2.0-2.25.1 中,您将在包名称中查找 hk2。在 2.26 中,您将在包名称中查找 jersey。另一个区别在于 to() 方法。在 2.0-2.25.1 中,您将使用 ValueFactoryProvider,而 2.26+,您将使用 ValueParamProvider

这是 ValueParamProvider 的示例实现(适用于 Jersey 2.26+)。 ValueFactoryProvider 的实现将非常相似

public class DeepObjectParamProvider implements ValueParamProvider {

    @Override
    public Function<ContainerRequest,?> getValueProvider(Parameter param) {

        if (param.isAnnotationPresent(DeepObjectParam.class) && isStringStringMap(param)) {
            return new DeepParamFunction(param);
        }
        return null;
    }

    private static boolean isStringStringMap(Parameter param) {
        if (!param.getRawType().equals(Map.class)) {
            return false;
        }
        ParameterizedType type = (ParameterizedType) param.getType();
        Type[] genericTypes = type.getActualTypeArguments();
        return genericTypes[0].equals(String.class) && genericTypes[1].equals(String.class);
    }

    @Override
    public PriorityType getPriority() {
        // Use HIGH otherwise it might not be used
        return Priority.HIGH;
    }

    private static class DeepParamFunction implements Function<ContainerRequest,Map<String,String>> {

        private final Parameter param;

        private DeepParamFunction(Parameter param) {
            this.param = param;
        }

        @Override
        public Map<String,String> apply(ContainerRequest request) {
            Map<String,String> map = new HashMap<>();

            DeepObjectParam anno = param.getAnnotation(DeepObjectParam.class);
            String paramName = anno.value();
            MultivaluedMap<String,String> params = request.getUriInfo().getQueryParameters();
            params.forEach((key,list) -> {
                // do parsing of params
            });

            return map;
        }
    }
}

有关完整运行 (2.26+) 的示例,请查看 this post。对于 2.26 之前的版本,我重构了该示例并将其发布到 this Gist

附注

在实现提供程序和调试时,不要对多次调用该方法感到惊讶。发生的情况是,在启动时,Jersey 将验证所有资源方法并确保所有参数都能够被处理。 Jersey 如何做到这一点是通过将每个参数传递给 all 提供者,直到到达一个不返回 null 的提供者。所以你拥有的资源方法越多,你的提供者被调用的次数就越多。有关详细说明,请参阅 this post