Ответ 1
Так что это можно сделать с помощью ParamConverter
/ParamConverterProvider
. Нам просто нужно ввести ResourceInfo
. Оттуда мы можем получить ресурс Method
и просто сделать некоторое отражение. Ниже приведен пример реализации, который я тестировал и работает по большей части.
import java.lang.reflect.Type;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.annotation.Annotation;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import javax.ws.rs.FormParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.InternalServerErrorException;
@Provider
public class Base32UUIDParamConverter implements ParamConverterProvider {
@Context
private javax.inject.Provider<ResourceInfo> resourceInfo;
private static final Set<Class<? extends Annotation>> ANNOTATIONS;
static {
Set<Class<? extends Annotation>> annots = new HashSet<>();
annots.add(QueryParam.class);
annots.add(FormParam.class);
ANNOTATIONS = Collections.<Class<? extends Annotation>>unmodifiableSet(annots);
}
@Override
public <T> ParamConverter<T> getConverter(Class<T> type,
Type type1,
Annotation[] annots) {
// Check if it is @FormParam or @QueryParam
for (Annotation annotation : annots) {
if (!ANNOTATIONS.contains(annotation.annotationType())) {
return null;
}
}
if (Base32UUID.class == type) {
return new ParamConverter<T>() {
@Override
public T fromString(String value) {
try {
Method method = resourceInfo.get().getResourceMethod();
Parameter[] parameters = method.getParameters();
Parameter actualParam = null;
// Find the actual matching parameter from the method.
for (Parameter param : parameters) {
Annotation[] annotations = param.getAnnotations();
if (matchingAnnotationValues(annotations, annots)) {
actualParam = param;
}
}
// null warning, but assuming my logic is correct,
// null shouldn't be possible. Maybe check anyway :-)
String paramName = actualParam.getName();
System.out.println("Param name : " + paramName);
Base32UUID uuid = new Base32UUID(value, paramName);
return type.cast(uuid);
} catch (Base32UUIDException ex) {
throw new BadRequestException(ex.getMessage());
} catch (Exception ex) {
throw new InternalServerErrorException(ex);
}
}
@Override
public String toString(T t) {
return ((Base32UUID) t).value;
}
};
}
return null;
}
private boolean matchingAnnotationValues(Annotation[] annots1,
Annotation[] annots2) throws Exception {
for (Class<? extends Annotation> annotType : ANNOTATIONS) {
if (isMatch(annots1, annots2, annotType)) {
return true;
}
}
return false;
}
private <T extends Annotation> boolean isMatch(Annotation[] a1,
Annotation[] a2,
Class<T> aType) throws Exception {
T p1 = getParamAnnotation(a1, aType);
T p2 = getParamAnnotation(a2, aType);
if (p1 != null && p2 != null) {
String value1 = (String) p1.annotationType().getMethod("value").invoke(p1);
String value2 = (String) p2.annotationType().getMethod("value").invoke(p2);
if (value1.equals(value2)) {
return true;
}
}
return false;
}
private <T extends Annotation> T getParamAnnotation(Annotation[] annotations,
Class<T> paramType) {
T paramAnnotation = null;
for (Annotation annotation : annotations) {
if (annotation.annotationType() == paramType) {
paramAnnotation = (T) annotation;
break;
}
}
return paramAnnotation;
}
}
Некоторые примечания о реализации
-
Самая важная часть - это то, как вводится
ResourceInfo
. Поскольку это нужно получить в контексте области запроса, я ввелjavax.inject.Provider
, что позволяет нам лениво получить объект. Когда мы на самом деле делаемget()
, оно будет в пределах области запроса.Осторожность в том, что он
get()
должен быть вызван внутри методаfromString
ParamConverter
. МетодgetConverter
ParamConverterProvider
вызывается много раз во время загрузки приложения, поэтому мы не можем попробовать и вызватьget()
в течение этого времени. -
java.lang.reflect.Parameter
класс, который я использовал, - это класс Java 8, поэтому, чтобы использовать эту реализацию, вам нужно работать на Java 8. Если вы не используете Java 8, этот пост может помочь в попытке получить имя параметра другим способом. -
В связи с вышеупомянутой точкой аргумент компилятора
-parameters
должен применяться при компиляции, чтобы иметь возможность получить доступ к формальному имени параметра, как указано здесь. Я просто положил его в maven-cmpiler-plugin, как указано в ссылке.<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <inherited>true</inherited> <configuration> <compilerArgument>-parameters</compilerArgument> <testCompilerArgument>-parameters</testCompilerArgument> <source>1.8</source> <target>1.8</target> </configuration> </plugin>
Если вы этого не сделаете, вызов
Parameter.getName()
приведет к тому, чтоargX
,X
будет индексом параметра. -
Реализация допускает только
@FormParam
и@QueryParam
. -
Важно отметить (что я усвоил трудный путь), то есть все исключения, которые не обрабатываются в
ParamConverter
(только применим к @QueryParam в этом случае), приведет к 404 без объяснения проблемы. Поэтому вам нужно убедиться, что вы обрабатываете свое исключение, если хотите другое поведение.
UPDATE
В приведенной выше версии есть ошибка:
// Check if it is @FormParam or @QueryParam
for (Annotation annotation : annots) {
if (!ANNOTATIONS.contains(annotation.annotationType())) {
return null;
}
}
Вышеупомянутое вызывается во время проверки модели, когда getConverter
вызывается для каждого параметра. Приведенный выше код работает только в одной аннотации. Если есть другая аннотация, кроме @QueryParam
или @FormParam
, скажем @NotNull
, она не сработает. Остальная часть кода в порядке. Он действительно работает в предположении, что будет больше одной аннотации.
Исправление для вышеуказанного кода будет чем-то вроде
boolean hasParamAnnotation = false;
for (Annotation annotation : annots) {
if (ANNOTATIONS.contains(annotation.annotationType())) {
hasParamAnnotation = true;
break;
}
}
if (!hasParamAnnotation) return null;