问题描述
我有一个Jersey 1.19.1资源,它实现了 @PUT
和 @POST
方法。 @PUT
方法需要一个JSON字符串作为输入/请求体,而 @POST
方法接受纯文本。
对于JSON映射,我使用Jackson 2.8。
I have a Jersey 1.19.1 resource that implements a @PUT
and @POST
method. The @PUT
method expects a JSON string as the input/request body, while the @POST
method accepts plain text.For the JSON mapping I am using Jackson 2.8.
由于资源定义为以这种方式工作,我不希望客户端被要求指定一个 Content-Type
请求标头,因为Jersey需要它来确定在请求体上使用哪个 ObjectMapper
。
Since the resource is defined to work this way, I don't want the client to be required to specify a Content-Type
request header, just because Jersey needs it to figure out which ObjectMapper
to use on the request body.
我想要的是告诉泽西岛使用这个 ObjectMapper
这个 @PUT
输入,或始终假设此输入将具有应用程序/ json
Content-Type
on this method。
What I want instead, is to tell Jersey "Use this ObjectMapper
for this @PUT
input", or "Always assume this input will have an application/json
Content-Type
on this method."
@Produces(MediaType.APPLICATION_JSON)
@Path("/some/endpoint/{id}")
public class MyResource {
@PUT
public JsonResult put(
@PathParam("id") String id,
// this should always be deserialized by Jackson, regardless of the `Content-Type` request header.
JsonInput input
) {
log.trace("PUT {}, {}, {}", id, input.foo, input.bar);
return new JsonResult("PUT result");
}
@POST
public JsonResult post(
@PathParam("id") String id,
// this should always be treated as plain text, regardless of the `Content-Type` request header.
String input
) {
log.trace("POST {}, {}", id, input);
return new JsonResult("POST result");
}
}
我只找到,但这不是我想要的,因为解决方案似乎应该要求客户端添加正确的 Content-Type
标题,或者手动进行对象映射。
I only found this answer, but that's not what I'm looking for, as the solution there seems to be that the client should be required to add the correct Content-Type
header, or otherwise do the object mapping manually.
推荐答案
我设法想出一个解决方法。我没有声明要在Jersey资源方法上使用哪个 ObjectMapper
,而是决定创建一个 ResourceFilter
,对应 ResourceFilterFactory
,以及注释类型。每当使用此类型注释资源类或方法时, ResourceFilter
将覆盖请求的 Content-Type
到任何是在注释的参数中声明。
I managed to come up with a workaround. Instead of declaring which ObjectMapper
to use on a Jersey resource method, I decided to create a ResourceFilter
, corresponding ResourceFilterFactory
, and an annotation type. Whenever a resource class or method is annotated with this type, the ResourceFilter
will override the request's Content-Type
to whatever is declared in the annotation's parameter.
这是我的代码:
OverrideInputType
注释:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OverrideInputType {
// What the Content-Type request header value should be replaced by
String value();
// which Content-Type request header values should not be replaced
String[] except() default {};
}
OverrideInputTypeResourceFilter
:
public class OverrideInputTypeResourceFilter implements ResourceFilter, ContainerRequestFilter {
private MediaType targetType;
private Set<MediaType> exemptTypes;
OverrideInputTypeResourceFilter(
@Nonnull String targetType,
@Nonnull String[] exemptTypes
) {
this.targetType = MediaType.valueOf(targetType);
this.exemptTypes = new HashSet<MediaType>(Lists.transform(
Arrays.asList(exemptTypes),
exemptType -> MediaType.valueOf(exemptType)
));
}
@Override
public ContainerRequest filter(ContainerRequest request) {
MediaType inputType = request.getMediaType();
if (targetType.equals(inputType) || exemptTypes.contains(inputType)) {
// unmodified
return request;
}
MultivaluedMap<String, String> headers = request.getRequestHeaders();
if (headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
headers.putSingle(HttpHeaders.CONTENT_TYPE, targetType.toString());
request.setHeaders((InBoundHeaders)headers);
}
return request;
}
@Override
public final ContainerRequestFilter getRequestFilter() {
return this;
}
@Override
public final ContainerResponseFilter getResponseFilter() {
// don't filter responses
return null;
}
}
OverrideInputTypeResourceFilterFactory
:
public class OverrideInputTypeResourceFilterFactory implements ResourceFilterFactory {
@Override
public List<ResourceFilter> create(AbstractMethod am) {
// documented to only be AbstractSubResourceLocator, AbstractResourceMethod, or AbstractSubResourceMethod
if (am instanceof AbstractSubResourceLocator) {
// not actually invoked per request, nothing to do
log.debug("Ignoring AbstractSubResourceLocator {}", am);
return null;
} else if (am instanceof AbstractResourceMethod) {
OverrideInputType annotation = am.getAnnotation(OverrideInputType.class);
if (annotation == null) {
annotation = am.getResource().getAnnotation(OverrideInputType.class);
}
if (annotation != null) {
return Lists.<ResourceFilter>newArrayList(
new OverrideInputTypeResourceFilter(annotation.value(), annotation.except()));
}
} else {
log.warn("Got an unexpected instance of {}: {}", am.getClass().getName(), am);
}
return null;
}
}
示例 MyResource
演示其使用:
@Produces(MediaType.APPLICATION_JSON)
@Path(/objects/{id}")
public class MyResource {
@PUT
// @Consumes(MediaType.APPLICATION_JSON)
@OverrideInputType(MediaType.APPLICATION_JSON)
public StatusResult put(@PathParam("id") int id, JsonObject obj) {
log.trace("PUT {}", id);
// do something with obj
return new StatusResult(true);
}
@GET
public JsonObject get(@PathParam("id") int id) {
return new JsonObject(id);
}
}
在泽西2中,你可以用一个后匹配 ContainerRequestFilters
In Jersey 2, you could do this with a post-matching ContainerRequestFilters
这篇关于Jersey:硬编码POST / PUT ObjectMapper,不需要Content-Type标头的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!