Jackson JsonParseExceptionMapper 和 JsonMappingExceptionMapper 阴影自定义映射器

Kys*_*van 7 java jersey spring-boot

我的项目使用 Spring Boot + Jersey 2。

我为 JsonParseException 创建了自定义 Jackson 映射器,但它没有被调用,而是使用了标准的 Jackson JsonParseExceptionMapper。

我的自定义映射器:

 package com.rmn.gfc.common.providers;
 import ...

 @Provider
 public class JsonParseExceptionHandler implements ExceptionMapper<JsonParseException> { 
    @Override
    public Response toResponse(JsonParseException exception) {
        return Response
                .status(Response.Status.BAD_REQUEST)
                //  entity ...
                .build();
    }
}
Run Code Online (Sandbox Code Playgroud)

我像这样注册我的映射器:

@Component
public class OrderServiceResourceConfig extends ResourceConfig {
    public OrderServiceResourceConfig() {
        packages("com.rmn.gfc.common.providers");
    }
}
Run Code Online (Sandbox Code Playgroud)

我确信映射器注册很好,因为这个包中的其他自定义映射器正在工作,但 JsonParseException 的一个映射器被 Jersey 标准 JsonParseExceptionMapper 遮住了。

我如何用我的实现覆盖标准的 Jackson JsonParseExceptionMapper?

cas*_*lin 8

你可以责怪 JacksonFeature

JacksonFeature如果JacksonJaxbJsonProvider未注册,则为 Jackson 异常注册默认异常映射器。而这正是你不想要的。

查看源代码的相关部分:

// Register Jackson.
if (!config.isRegistered(JacksonJaxbJsonProvider.class)) {
    // add the default Jackson exception mappers
    context.register(JsonParseExceptionMapper.class);
    context.register(JsonMappingExceptionMapper.class);
    ...
}
Run Code Online (Sandbox Code Playgroud)

Spring Boot 注册了 JacksonFeature

Spring Boot 的JerseyAutoConfiguration类将注册JacksonFeature它是否在类路径上。查看源代码的相关部分:

@ConditionalOnClass(JacksonFeature.class)
@ConditionalOnSingleCandidate(ObjectMapper.class)
@Configuration
static class JacksonResourceConfigCustomizer {

    ...

    @Bean
    public ResourceConfigCustomizer resourceConfigCustomizer(
            final ObjectMapper objectMapper) {
        addJaxbAnnotationIntrospectorIfPresent(objectMapper);
        return (ResourceConfig config) -> {
            config.register(JacksonFeature.class);
            config.register(new ObjectMapperContextResolver(objectMapper),
                    ContextResolver.class);
        };
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

解决方法

作为一种解决方法,您可以注册JacksonJaxbJsonProvider然后注册您的自定义异常映射器(或者只是注释它们以@Provider被 Jersey 自动发现):

// Register Jackson.
if (!config.isRegistered(JacksonJaxbJsonProvider.class)) {
    // add the default Jackson exception mappers
    context.register(JsonParseExceptionMapper.class);
    context.register(JsonMappingExceptionMapper.class);
    ...
}
Run Code Online (Sandbox Code Playgroud)

看看文档是怎么说的JacksonJaxbJsonProvider

JSON 内容类型提供程序自动配置为使用 Jackson 和 JAXB 注释(按优先级顺序)。其他功能与JacksonJsonProvider.

替代方案

或者,您可以摆脱jersey-media-json-jackson工件并使用jackson-jaxrs-json-provider。有了这个,您将摆脱,JacksonFeature然后您可以注册自己的异常映射器。

这个答案中提到了。

什么似乎是正确的解决方案

请参阅JAX-RS 2.1 规范中的以下引用:

4.1.3 优先事项

应用程序提供的提供程序使开发人员能够扩展和自定义 JAX-RS 运行时。因此,如果需要一个应用程序提供的提供程序,则必须始终优先于预打包的提供程序。

应用程序提供的提供者可以用@Priority. 如果有两个或更多提供者是某个任务的候选者,则选择具有最高优先级的提供者:在这种情况下,最高优先级被定义为具有最低值的提供者。即@Priority(1)高于@Priority(10)。如果两个或多个提供者符合条件并且具有相同的优先级,则以依赖于实现的方式选择一个。

所有应用程序提供的提供程序的默认优先级是javax.ws.rs.Priorities.USER。对于过滤器和拦截器,关于优先级的一般规则是不同的,因为这些提供者被收集到链中。

正如 Kysil Ivan 的回答中所指出的,编写自己的异常映射器并赋予它高优先级,例如1. 如果您使用自动发现,只需使用@Provider和注释即可@Priority

@ConditionalOnClass(JacksonFeature.class)
@ConditionalOnSingleCandidate(ObjectMapper.class)
@Configuration
static class JacksonResourceConfigCustomizer {

    ...

    @Bean
    public ResourceConfigCustomizer resourceConfigCustomizer(
            final ObjectMapper objectMapper) {
        addJaxbAnnotationIntrospectorIfPresent(objectMapper);
        return (ResourceConfig config) -> {
            config.register(JacksonFeature.class);
            config.register(new ObjectMapperContextResolver(objectMapper),
                    ContextResolver.class);
        };
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

如果您手动注册您的提供者,您可以给您的提供者一个绑定优先级

@Component
public class OrderServiceResourceConfig extends ResourceConfig {

    public OrderServiceResourceConfig() {
        packages("com.rmn.gfc.common.providers");
        register(JacksonJaxbJsonProvider.class);
        // Register other providers
    }
}
Run Code Online (Sandbox Code Playgroud)


Kys*_*van 5

我找到了适合我的解决方案。在您的自定义映射器上
使用javax.annotation.Priority以使其覆盖 Jackson 默认映射器,例如:

@Provider
@Priority(1)
public class JsonParseExceptionHandler implements ExceptionMapper<JsonParseException> {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您通过 ResourceConfig 注册 JAX-RS 组件,您可以像这样指定优先级:

public class MyResourceConfig extends ResourceConfig {
    public MyResourceConfig() {
        register(JsonMappingExceptionHandler.class, 1);
        register(JsonParseExceptionHandler.class, 1);
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

数字越小优先级越高。
javax.ws.rs.Priorities有一些预定义的常量用于优先级。