【问题标题】:Jackson JsonParseExceptionMapper and JsonMappingExceptionMapper shadows custom mapperJackson JsonParseExceptionMapper 和 JsonMappingExceptionMapper 阴影自定义映射器
【发布时间】:2017-08-03 08:13:56
【问题描述】:

我的项目使用 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();
    }
}

我这样注册我的映射器:

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

我确信映射器注册没问题,因为该包中的其他自定义映射器正在工作,但 JsonParseException 的一个映射器被 Jersey 标准 JsonParseExceptionMapper 遮蔽。

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

【问题讨论】:

    标签: java spring-boot jersey


    【解决方案1】:

    你可以责怪JacksonFeature

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

    相关部分见source code

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

    Spring Boot 注册了JacksonFeature

    Spring Boot 的 JerseyAutoConfiguration 类将注册 JacksonFeature 如果它在类路径上。相关部分见source code

    @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);
            };
        }
    
        ...
    }
    

    解决方法

    作为一种解决方法,您可以注册 JacksonJaxbJsonProvider,然后注册您的自定义异常映射器(或者只需使用 @Provider 对其进行注释以让 Jersey 自动发现):

    @Component
    public class OrderServiceResourceConfig extends ResourceConfig {
    
        public OrderServiceResourceConfig() {
            packages("com.rmn.gfc.common.providers");
            register(JacksonJaxbJsonProvider.class);
            // Register other providers
        }
    }
    

    查看文档中关于 JacksonJaxbJsonProvider 的内容:

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

    替代解决方案

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

    answer中提到过。

    似乎是正确的解决方案

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

    4.1.3 优先事项

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

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

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

    正如 Kysil Ivan 的 answer 中所指出的,编写您自己的异常映射器并为其赋予高优先级,例如 1。如果您使用自动发现,只需使用 @Provider@Priority 对其进行注释即可。

    @Provider
    @Priority(1)
    public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
        ...
    }
    

    如果你手动注册你的提供者,你可以give your provider a binding priority:

    @ApplicationPath("/")
    public class MyResourceConfig extends ResourceConfig {
    
        public MyResourceConfig() {
            register(JsonParseExceptionMapper.class, 1);
        }
    }
    

    【讨论】:

    • 我不确定注册 JacksonJaxbJsonProvider 是否适合我。我害怕副作用。我想找到更有针对性的解决方案,只是注册我的映射器而不是默认值。谢谢。
    • @KysilIvan 请再次查看我的答案。已更新。
    • 我不清楚 JacksonJaxbJsonProvider 注册的后果,不想阻止某些东西,因为应用程序很大而且不是新的。感谢您的回复,它内容丰富且很有帮助,但遗憾的是没有更简单的方法来解决这个问题。
    【解决方案2】:

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

    @Provider
    @Priority(1)
    public class JsonParseExceptionHandler implements ExceptionMapper<JsonParseException> {
        // ...
    }
    

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

    public class MyResourceConfig extends ResourceConfig {
        public MyResourceConfig() {
            register(JsonMappingExceptionHandler.class, 1);
            register(JsonParseExceptionHandler.class, 1);
            // ...
        }
    }
    

    较低的数字表示最高优先级。
    javax.ws.rs.Priorities 有一些预定义的优先级常量。

    【讨论】:

    • 这是一个很棒的解决方案!
    • 哪个版本的球衣?
    • @Brice Jersey 2.x
    • 是的,但是 2.21、2.22、...?我没有看到任何关于泽西岛的文档,据我所知还没有,请参阅jersey/jersey#2994
    • 我有 jackson-core v. 2.8.6
    猜你喜欢
    • 2012-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-23
    • 1970-01-01
    • 2014-01-16
    • 2012-12-31
    相关资源
    最近更新 更多