是否可以在Jersey中为单个对象/变量提供多个 @QueryParam
个键?
实际:
@POST
public Something getThings(@QueryParam("customer-number") Integer n) {
...
}
所以,如果我在其工作的URL之后添加?customer-number=3
。
预期:
如果我添加以下任何值,我想获得上述行为:
?customer-number=3
?customerNumber=3
?customerNo=3
观测值:
QueryParam
注释如下所示:
...
public @interface QueryParam {
String value();
}
因此,它不能接受多个String值(如@Produces
)。
以下方法允许用户同时使用具有相同含义的多个键(并且我希望它们之间具有" OR"条件):
@POST
public Something getThings(@QueryParam("customer-number") Integer n1,
@QueryParam("customerNumber") Integer n2,
@QueryParam("customerNo") Integer n3) {
...
}
这样的事情不起作用:
@POST
public Something getThings(@QueryParam("customer-number|customerNumber|customerNo") Integer n) {
...
}
我该怎么做?
详细信息:
答案 0 :(得分:1)
使用自定义@BeanParam
:
首先定义自定义bean,将所有查询参数合并为:
class MergedIntegerValue {
private final Integer value;
public MergedIntegerValue(
@QueryParam("n1") Integer n1,
@QueryParam("n2") Integer n2,
@QueryParam("n3") Integer n3) {
this.value = n1 != null ? n1
: n2 != null ? n2
: n3 != null ? n3
: null;
// Throw an exception if value == null ?
}
public Integer getValue() {
return value;
}
}
然后在资源方法中将其与@BeanParam
一起使用:
public Something getThings(
@BeanParam MergedIntegerValue n) {
// Use n.getValue() ...
}
参考:https://jersey.java.net/documentation/latest/user-guide.html#d0e2403
答案 1 :(得分:1)
老实说:这不是Web服务的设计方式。你制定了严格的合同,客户和服务器都遵循这个合同;你定义了一个参数,那就是它。
但当然,这将是一个完美的世界,你可以自由地决定将会发生什么。因此,如果您必须允许三个参数,那么您必须签订合同。这是遵循方法#2的一种方法,我必须提供这种方法,而不能为goofs进行测试:
public Something getThings(@QueryParam("customer-number") Integer n1,
@QueryParam("customerNumber") Integer n2,
@QueryParam("customerNo") Integer n3) throws YourFailureException {
Integer customerNumber = getNonNullValue("Customer number", n1, n2, n3);
// things with stuff
}
private static Integer getNonNullValue(String label, Integer... params) throws YourFailureException {
Integer value = null;
for(Integer choice : params){
if(choice != null){
if(value != null){
// this means there are at least two query parameters passed with a value
throw new YourFailureException("Ambiguous " + label + " parameters");
}
value = choice;
}
}
if(value == null){
throw new YourFailureException("Missing " + label + " parameter");
}
return value;
}
所以基本上拒绝任何没有通过其中一个参数的调用,并让异常映射器将您抛出的异常转换为4xx范围内的HTTP响应代码。
(我将getNonNullValue()方法设为静态,它让我觉得它是一个可重用的实用函数。)
答案 2 :(得分:1)
您可以创建自定义注释。我不会过多地介绍如何操作,您可以看到this documentation或this post。基本上它依赖于与通常的泽西依赖注入不同的基础设施。您可以在Jersey项目中看到this post。这是所有注射提供者生活的地方,处理@XxxParam
注射。如果检查源代码,您将看到实现完全相同。我上面提供的两个链接遵循相同的模式,以及下面的代码。
我所做的是创建了一个自定义注释
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface VaryingParam {
String value();
@SuppressWarnings("AnnotationAsSuperInterface")
public static class Factory
extends AnnotationLiteral<VaryingParam> implements VaryingParam {
private final String value;
public static VaryingParam create(final String newValue) {
return new Factory(newValue);
}
public Factory(String newValue) {
this.value = newValue;
}
@Override
public String value() {
return this.value;
}
}
}
我有一个工厂来创建它可能看起来很奇怪,但这是实现下面代码所必需的,我将分割String的值,最后为每个分割值创建一个新的注释实例。
以下是ValueFactoryProvider
(如果您已阅读上述任一文章,则会看到自定义方法参数注入需要这样做)。这是一个很大的类,只是因为我将所有必需的类放在一个类中,遵循您在Jersey项目中看到的模式。
public class VaryingParamValueFactoryProvider extends AbstractValueFactoryProvider {
@Inject
public VaryingParamValueFactoryProvider(
final MultivaluedParameterExtractorProvider mpep,
final ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
@Override
protected Factory<?> createValueFactory(final Parameter parameter) {
VaryingParam annotation = parameter.getAnnotation(VaryingParam.class);
if (annotation == null) {
return null;
}
String value = annotation.value();
if (value == null || value.length() == 0) {
return null;
}
String[] variations = value.split("\\s*\\|\\s*");
return new VaryingParamFactory(variations, parameter);
}
private static Parameter cloneParameter(final Parameter original, final String value) {
Annotation[] annotations = changeVaryingParam(original.getAnnotations(), value);
Parameter clone = Parameter.create(
original.getRawType(),
original.getRawType(),
true,
original.getRawType(),
original.getRawType(),
annotations);
return clone;
}
private static Annotation[] changeVaryingParam(final Annotation[] annos, final String value) {
for (int i = 0; i < annos.length; i++) {
if (annos[i] instanceof VaryingParam) {
annos[i] = VaryingParam.Factory.create(value);
break;
}
}
return annos;
}
private class VaryingParamFactory extends AbstractContainerRequestValueFactory<Object> {
private final String[] variations;
private final Parameter parameter;
private final boolean decode;
private final Class<?> paramType;
private final boolean isList;
private final boolean isSet;
VaryingParamFactory(final String[] variations, final Parameter parameter) {
this.variations = variations;
this.parameter = parameter;
this.decode = !parameter.isEncoded();
this.paramType = parameter.getRawType();
this.isList = paramType == List.class;
this.isSet = paramType == Set.class;
}
@Override
public Object provide() {
MultivaluedParameterExtractor<?> e = null;
try {
Object value = null;
MultivaluedMap<String, String> params
= getContainerRequest().getUriInfo().getQueryParameters(decode);
for (String variant : variations) {
e = get(cloneParameter(parameter, variant));
if (e == null) {
return null;
}
if (isList) {
List list = (List<?>) e.extract(params);
if (value == null) {
value = new ArrayList();
}
((List<?>) value).addAll(list);
} else if (isSet) {
Set set = (Set<?>) e.extract(params);
if (value == null) {
value = new HashSet();
}
((Set<?>) value).addAll(set);
} else {
value = e.extract(params);
if (value != null) {
return value;
}
}
}
return value;
} catch (ExtractorException ex) {
if (e == null) {
throw new ParamException.QueryParamException(ex.getCause(),
parameter.getSourceName(), parameter.getDefaultValue());
} else {
throw new ParamException.QueryParamException(ex.getCause(),
e.getName(), e.getDefaultValueString());
}
}
}
}
private static class Resolver extends ParamInjectionResolver<VaryingParam> {
public Resolver() {
super(VaryingParamValueFactoryProvider.class);
}
}
public static class Binder extends AbstractBinder {
@Override
protected void configure() {
bind(VaryingParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(VaryingParamValueFactoryProvider.Resolver.class)
.to(new TypeLiteral<InjectionResolver<VaryingParam>>() {
})
.in(Singleton.class);
}
}
}
您需要在泽西岛注册此课程“Binder
(课程底层)”才能使用它。
此类与Jersey this package的区别在于,它不是仅处理注释的单个String值,而是拆分该值,并尝试从查询参数映射中提取值。找到的第一个值将被返回。如果参数是List
或Set
,它会继续查找所有选项,并将它们添加到列表中。
在大多数情况下,这保留了@XxxParam
注释所期望的所有功能。唯一难以实现的(因此我省略了支持此用例)是多个参数,例如
@GET
@Path("multiple")
public String getMultipleVariants(@VaryingParam("param-1|param-2|param-3") String value1,
@VaryingParam("param-1|param-2|param-3") String value2) {
return value1 + ":" + value2;
}
我实际上并不认为它应该难以实现,如果你真的需要它,只需要创建一个新的MultivaluedMap
,如果找到它就删除一个值。这将在上面provide()
的{{1}}方法中实施。如果您需要此用例,则可以使用VaryingParamFactory
或List
代替。
使用Jersey Test Framework查看完整测试用例QueryParamValueFactoryProvider
(相当长)。您可以在Set
中查看我测试的所有用例,并在测试QueryTestResource
方法中使用Binder
注册ResourceConfig
。