如何扩展 Jersey 的参数注释?

How do I extend Jersey's param annotations?

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

是否可以将其添加为扩展名?如果是,怎么做?

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

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

并让 Jersey 注入地图。

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

对于两个 classes,实现将是相似的。有一种实现方法需要一个 Parameter 参数。我们使用此 Parameter 来检查此提供程序是否能够处理此类参数。如果检查通过,则该方法应该 return 提供实际参数的 FactoryFunction(取决于版本)。如果检查失败,它应该 return 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 参数。它将解析查询参数,然后 return Map.

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

注册一个 AbstractBinder
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 class,但导入会有所不同。在 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. For versions earlier than 2.26, I've refactored that example and posted it to this Gist

P.S.

在实现提供者和调试时,当多次调用该方法时不要感到惊讶。发生的事情是,在启动时,Jersey 将验证所有资源方法并确保能够处理所有参数。 Jersey 如何做到这一点是通过将每个参数传递给 所有 提供者,直到到达一个不 return 为空的提供者。所以你拥有的资源方法越多,你的提供者被调用的次数就越多。有关更多详细信息,请参阅