我在 REST API 中使用 Jersey 2.10 和 Jackson 序列化/反序列化功能。
我的想法是让我的 REST API 始终返回标准 JSON 错误响应。为此,我有 ExceptionMapper 类,可以为 Jersey 应用程序中抛出的任何异常构建正确的 json 错误响应。我还有一个 jsp,它会生成相同类型的 JSON 响应,我将其注册为 web.xml 中的错误页面,涵盖了加载 Jersey 之前可能出现的所有错误。
但是有一种情况,我的异常映射器和生成 json 的 jsp 都不起作用,即向 POST REST 端点发送格式错误的 json 时,该端点仅返回以下消息:
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Content-Type: text/plain
Content-Length: 210
Date: Tue, 24 Jun 2014 22:14:11 GMT
Connection: close
Can not deserialize instance of com.example.rest.User[] out of START_OBJECT token
at [Source: org.glassfish.jersey.message.internal.EntityInputStream@1dcccac; line: 1, column: 1]
我怎样才能让 Jersey 返回我的自定义错误响应而不是这个?
更新:
根据@Lucasz的回答,我做了更多研究,发现com.fasterxml.jackson.jaxrs.base包内定义了两个异常映射器(https://github.com/FasterXML/jackson-jaxrs- providers/tree/master/base/src/main/java/com/fasterxml/jackson/jaxrs/base) JsonMappingExceptionMapper 和 JsonParseExceptionMapper 似乎正在隐藏我的自定义映射器。
如何取消注册这些映射器?这就是我目前注册映射器的方式:
@ApplicationPath("/")
public class MyApp extends ResourceConfig{
public SyntheticAPIApp() {
packages("com.example.resource", "com.example.mapper");
register(org.glassfish.jersey.jackson.JacksonFeature.class);
}
}
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.core.JsonProcessingException;
@Provider
public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException>{
public static class Error {
public String key;
public String message;
}
@Override
public Response toResponse(JsonProcessingException exception) {
Error error = new Error();
error.key = "bad-json";
error.message = exception.getMessage();
return Response.status(Status.BAD_REQUEST).entity(error).build();
}
}
并且成功了。
更新:将 JsonParseException 更改为 JsonProcessingException(更通用)
更新2: 为了避免注册不需要的映射器,请替换
register(org.glassfish.jersey.jackson.JacksonFeature.class);
与
register(com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.class);
查看
JacksonFeature 的源代码,您就会明白发生了什么。
(这里的high 表示一个低的、严格的正数)来注释自定义异常映射器应该足够了。为了覆盖
org.glassfish.jersey.media:jersey-media-json-jackson
提供的“默认”映射器(到
register(JacksonFeature.class)
),我们只提供这两个自定义映射器:
@Provider
@Priority(1)
public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
/* ... */
}
@Provider
@Priority(1)
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
/* ... */
}
不幸的是,JAX-RS 2 规范无视优先级,仅声明:
选择异常映射提供者来映射异常时, 实现必须使用其泛型类型是异常的最近超类的提供者。不注册
JacksonFeature.class
并注册
JacksonJaxbJsonProvider.class
,如另一个答案中提到的那样,并没有产生一致的结果。
org.glassfish.jersey.spi.ExtendedExceptionMapper
,如https://jersey.java.net/documentation/latest/representations.html 中所述。 此外,Jersey 正在检查异常映射器,它尽可能接近抛出的异常(来自
org.glassfish.jersey.internal.ExceptionMapperFactory
):
for (final ExceptionMapperType mapperType : exceptionMapperTypes) {
final int d = distance(type, mapperType.exceptionType);
if (d >= 0 && d <= minDistance) {
final ExceptionMapper<T> candidate = mapperType.mapper.getService();
if (isPreferredCandidate(exceptionInstance, candidate, d == minDistance)) {
mapper = candidate;
minDistance = d;
if (d == 0) {
// slight optimization: if the distance is 0, it is already the best case, so we can exit
return mapper;
}
}
}
}
因此,我需要准确地映射异常,而不是更一般的异常。
最后,我的提供商如下所示:
@Provider
public final class JsonParseExceptionExceptionHandler implements ExtendedExceptionMapper<JsonParseException> {
@Override
public Response toResponse(final JsonParseException exception) {
exception.printStackTrace();
return Response.status(Response.Status.BAD_REQUEST).entity("JSON nicht in korrektem Format.").build();
}
@Override
public boolean isMappable(final JsonParseException arg0) {
return true;
}
}
我使用了“jackson-jaxrs-json-provider 2.8.8”和JAX-RS 2.0
应用程序类 - 您需要注册您的 ExceptionMapper 实现类:
@ApplicationPath("pathApplication")
public class ApplicationConfiguration extends Application{
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
resources.add(YourJAXRSClass.class);
resources.add(JsonJacksonEM.class); //ExceptionMapper class implementation
//others resources that you need...
return resources;
}
}
ExceptionMapper类实现:
@Provider
public class JsonJacksonEM implements ExceptionMapper<JsonParseException>{
@Override
public Response toResponse(JsonParseException exception) {
//you can return a Response in the way that you want!
return Response.ok(new YourObject()).build();
}
}
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
</param-value>
</init-param>
com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider
的解决方案并没有多大帮助。当我深入研究时,我发现 Jersey 默认情况下
org.glassfish.jersey.jackson.JacksonFeature
是由 Jersey 注册的,如果依赖项
jersey-media-json-jackson
即使没有注册它的显式声明(不确定自动注册是从哪个版本实现的,我猜)至少从 Jersey 2.29 开始)存在于类路径中。
JacksonFeature
依次自动注册
JsonParseExceptionMapper
和
JsonMappingExceptionMapper
。由于这些默认的 JSON 异常映射器,所有与 JSON 相关的异常都不会重定向到
custom exception mapper
幸运的是,Jersey 2.29.1 添加了对注册 JacksonFeature 的支持,而无需异常处理程序。链接@Provider
public class ApplicationConfig extends Application {
@Override
public Set<Object> getSingletons() {
Set<Object> singletons = super.getSingletons();
singletons.add(JacksonFeature.withoutExceptionMappers());
return singletons;
}
}
上面的代码片段将覆盖 Jersey 注册的默认 JacksonFeature
。通过这样做,所有与 JSON 相关的异常将被重定向到应用程序中存在的自定义异常映射器。