- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我正在使用的 API 已决定接受 UUID 作为 Base32 编码字符串,而不是 UUID.fromString()
的标准十六进制破折号分隔格式。期望。这意味着我不能简单地将 @QueryParam UUID myUuid
写为方法参数,因为转换会失败。
我正在通过使用不同的 fromString
转换器编写一个自定义对象来解决这个问题,以便与 Jersey @QueryString
和 @FormParam
注释。我希望能够在 fromString
方法中访问转换的上下文,以便我可以提供更好的错误消息。现在,我所能做的就是:
public static Base32UUID fromString(String uuidString) {
final UUID uuid = UUIDUtils.fromBase32(uuidString, false);
if (null == uuid) {
throw new InvalidParametersException(ImmutableList.of("Invalid uuid: " + uuidString));
}
return new Base32UUID(uuid);
}
我希望能够公开哪个参数具有无效的 UUID,因此我记录的异常和返回的用户错误非常清楚。理想情况下,我的转换方法会有一个额外的详细信息参数,如下所示:
public static Base32UUID fromString(
String uuidString,
String parameterName // New parameter?
) {
final UUID uuid = UUIDUtils.fromBase32(uuidString, false);
if (null == uuid) {
throw new InvalidParametersException(ImmutableList.of("Invalid uuid: " + uuidString
+ " for parameter " + parameterName));
}
return new Base32UUID(uuid);
}
但这会破坏 by-convention means that Jersey finds a parsing method :
- Have a static method named
valueOf
orfromString
that accepts a single String argument (see, for example,Integer.valueOf(String)
andjava.util.UUID.fromString(String))
;
我还看过 ParamConverterProvider
也可以注册以提供转换,但它似乎也没有添加足够的上下文。它提供的最接近的是注释数组,但据我所知,您不能从那里回溯以确定注释在哪个变量或方法上。我找到了 this和 this示例,但它们没有有效地使用 Annotations[]
参数或公开我可以看到的任何转换上下文。
有什么办法可以得到这些信息吗?或者我是否需要回退到端点方法中的显式转换调用?
如果有所不同,我使用的是 Dropwizard 0.8.0,它使用的是 Jersey 2.16 和 Jetty 9.2.9.v20150224。
最佳答案
所以这可以用ParamConverter
来完成/ParamConverterProvider
.我们只需要注入(inject)一个 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;
}
}
关于实现的一些说明
最重要的部分是如何注入(inject)ResourceInfo
。由于这需要在请求范围上下文中访问,因此我注入(inject)了 javax.inject.Provider
,这允许我们懒惰地检索对象。当我们真正执行get()
时,它会在一个请求范围内。
需要注意的是get()
必须在ParamConverter<的
。 fromString
方法中调用ParamConverterProvider
的 getConverter
方法在应用程序加载期间被多次调用,因此我们不能在此期间尝试调用 get()
。
java.lang.reflect.Parameter
我使用的类是 Java 8 类,因此为了使用此实现,您需要使用 Java 8。如果您不使用 Java 8,this post可能有助于尝试以其他方式获取参数名称。
关于以上一点,编译时需要应用编译器参数-parameters
,以便能够访问形式参数名称,如here所指出的那样.我只是按照链接中指出的那样将它放在 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
。
需要注意的一件重要事情 ( that I learned the hard way ) 是所有未在 ParamConverter
中处理的异常(在这种情况下仅适用于 @QueryParam),将导致没有解释问题的 404。因此,如果您想要不同的行为,则需要确保处理异常。
上面的实现有一个bug:
// 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;
关于java - 有什么方法可以知道在 Jersey @__Param fromString 处理程序中正在解析哪个参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31522414/
我想了解 Ruby 方法 methods() 是如何工作的。 我尝试使用“ruby 方法”在 Google 上搜索,但这不是我需要的。 我也看过 ruby-doc.org,但我没有找到这种方法。
Test 方法 对指定的字符串执行一个正则表达式搜索,并返回一个 Boolean 值指示是否找到匹配的模式。 object.Test(string) 参数 object 必选项。总是一个
Replace 方法 替换在正则表达式查找中找到的文本。 object.Replace(string1, string2) 参数 object 必选项。总是一个 RegExp 对象的名称。
Raise 方法 生成运行时错误 object.Raise(number, source, description, helpfile, helpcontext) 参数 object 应为
Execute 方法 对指定的字符串执行正则表达式搜索。 object.Execute(string) 参数 object 必选项。总是一个 RegExp 对象的名称。 string
Clear 方法 清除 Err 对象的所有属性设置。 object.Clear object 应为 Err 对象的名称。 说明 在错误处理后,使用 Clear 显式地清除 Err 对象。此
CopyFile 方法 将一个或多个文件从某位置复制到另一位置。 object.CopyFile source, destination[, overwrite] 参数 object 必选
Copy 方法 将指定的文件或文件夹从某位置复制到另一位置。 object.Copy destination[, overwrite] 参数 object 必选项。应为 File 或 F
Close 方法 关闭打开的 TextStream 文件。 object.Close object 应为 TextStream 对象的名称。 说明 下面例子举例说明如何使用 Close 方
BuildPath 方法 向现有路径后添加名称。 object.BuildPath(path, name) 参数 object 必选项。应为 FileSystemObject 对象的名称
GetFolder 方法 返回与指定的路径中某文件夹相应的 Folder 对象。 object.GetFolder(folderspec) 参数 object 必选项。应为 FileSy
GetFileName 方法 返回指定路径(不是指定驱动器路径部分)的最后一个文件或文件夹。 object.GetFileName(pathspec) 参数 object 必选项。应为
GetFile 方法 返回与指定路径中某文件相应的 File 对象。 object.GetFile(filespec) 参数 object 必选项。应为 FileSystemObject
GetExtensionName 方法 返回字符串,该字符串包含路径最后一个组成部分的扩展名。 object.GetExtensionName(path) 参数 object 必选项。应
GetDriveName 方法 返回包含指定路径中驱动器名的字符串。 object.GetDriveName(path) 参数 object 必选项。应为 FileSystemObjec
GetDrive 方法 返回与指定的路径中驱动器相对应的 Drive 对象。 object.GetDrive drivespec 参数 object 必选项。应为 FileSystemO
GetBaseName 方法 返回字符串,其中包含文件的基本名 (不带扩展名), 或者提供的路径说明中的文件夹。 object.GetBaseName(path) 参数 object 必
GetAbsolutePathName 方法 从提供的指定路径中返回完整且含义明确的路径。 object.GetAbsolutePathName(pathspec) 参数 object
FolderExists 方法 如果指定的文件夹存在,则返回 True;否则返回 False。 object.FolderExists(folderspec) 参数 object 必选项
FileExists 方法 如果指定的文件存在返回 True;否则返回 False。 object.FileExists(filespec) 参数 object 必选项。应为 FileS
我是一名优秀的程序员,十分优秀!