Вы можете создать пользовательскую аннотацию. Я не буду слишком много рассказывать о том, как это сделать, вы можете увидеть this post или this post. В основном он опирается на другую инфраструктуру, чем обычная инъекция зависимостей с Джерси. Вы можете увидеть this package из проекта Джерси. Именно там живут все поставщики инъекций, которые обрабатывают инъекции @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, и в конечном итоге создайте новый экземпляр аннотации для каждого значения split.
Вот ValueFactoryProvider
(который, если вы прочитали одну из приведенных выше статей, вы увидите, что это необходимо для инкрементной настройки параметров метода). Это большой класс, только потому, что я поместил все необходимые классы в один класс, следуя шаблону, который вы видите в проекте Джерси.
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 QueryParamValueFactoryProvider
, так это то, что вместо обработки только одного значения 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()
вышеприведенного значения VaryingParamFactory
. Если вам нужен этот вариант использования, вы можете просто использовать List
или Set
.
См. this GitHub Gist (это довольно длинный) для полного тестового примера, с использованием тестовой системы Джерси. Вы можете увидеть все случаи использования, которые я тестировал в QueryTestResource
, и где я регистрирую Binder
с помощью ResourceConfig
в методе теста configure()
.