You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot自定义Jackson转换器配置问题:校验与响应格式冲突

SpringBoot 2.7.14自定义Jackson转换器问题解答

背景

基于SpringBoot v2.7.14实现自定义Jackson转换器,要求反序列化时遇到未知属性抛出异常。架构为:前端 → FirstBackend → SecondBackend。

自定义转换器代码

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJackson2HttpMessageConverter(objectMapper()));
    }

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        MappingJackson2HttpMessageConverter bean = new MappingJackson2HttpMessageConverter();
        bean.setObjectMapper(objectMapper);
        return bean;
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
    }
}

FirstBackend接口代码

@Autowired
private DataWebClient dataWebClient;

@PostMapping(value = "/fullText", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Mono<String>> searchInFullTextMode(@RequestBody FullTextSearchQuery request,
            @RequestParam(value = "searchEngineType") String searchEngineType,
            @RequestParam(defaultValue = "false", required = false) boolean searhcCategory) throws JsonProcessingException {

        if (!StringUtils.hasLength(request.getText()) && searhcCategory) {
            throw new RequiredParameterException("required parameter....");
        }

        Mono<String> result = dataWebClient.searchInFullTextMode(request, searchEngineType);
        return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result);
    }

SecondBackend数据获取代码

import org.springframework.web.reactive.function.client.WebClient;
import com.fasterxml.jackson.databind.json.JsonMapper;

private static final JsonMapper MAPPER = JsonMapper.builder().propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE).build();

@Autowired
private WebClient webClient;

public Mono<String> searchInFullTextMode(FullTextSearchQuery fullTextSearchQuery, SearchEngine searchEngine) throws JsonProcessingException {
        return webClient.post().uri(searchEngine.getUrl() + "/fulltext")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(MAPPER.writeValueAsString(fullTextSearchQuery)))
                .retrieve()
                .onStatus(HttpStatus::isError, WebClientHelper::handleDataResponseException)
                .bodyToMono(String.class);
    }

疑问解答

1. 响应式获取数据的方式与转换器是否存在关联?

两者的消息转换器体系相互独立:

  • 当前WebConfig是配置Spring MVC(Servlet栈)的消息转换器,负责处理前端与FirstBackend之间的请求反序列化、响应序列化。
  • FirstBackend调用SecondBackend使用的WebClient属于Reactive栈,它有独立的消息转换器配置,默认不会复用WebMvc的ObjectMapper,所以当前自定义转换器对WebClient的序列化/反序列化逻辑没有影响。

2. 转换器配置与响应格式的矛盾问题解决

问题原因

  • 重写configureMessageConverters时响应转义:@EnableWebMvc会关闭Spring Boot的WebMvc自动配置,默认的StringHttpMessageConverter未被添加。仅配置自定义Jackson转换器后,当接口返回Mono<String>时,Jackson会把String对象序列化为JSON,导致输出转义后的字符串(例如"{\"key\":\"value\"}");但此时自定义ObjectMapper生效,未知属性校验正常。
  • 重写extendMessageConverters时校验失效:该方法是在默认转换器列表基础上扩展,默认的Jackson转换器会优先使用Spring Boot自动配置的ObjectMapper(未开启FAIL_ON_UNKNOWN_PROPERTIES),自定义转换器因顺序靠后未被选中处理请求反序列化,导致校验失效。

解决办法

方案1:修复configureMessageConverters配置,保留必要转换器

移除@EnableWebMvc(无需完全接管WebMvc配置时),或手动添加StringHttpMessageConverter避免响应转义:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 添加自定义Jackson转换器,处理JSON序列化/反序列化
        converters.add(mappingJackson2HttpMessageConverter(objectMapper()));
        // 添加String转换器,直接输出字符串,避免转义
        converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
    }

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        MappingJackson2HttpMessageConverter bean = new MappingJackson2HttpMessageConverter();
        bean.setObjectMapper(objectMapper);
        return bean;
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
    }
}
方案2:使用extendMessageConverters,确保自定义转换器优先生效

遍历默认转换器列表,替换默认Jackson转换器的ObjectMapper,确保校验规则生效:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 替换默认Jackson转换器的ObjectMapper
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                ((MappingJackson2HttpMessageConverter) converter).setObjectMapper(objectMapper());
                break;
            }
        }
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
    }
}
额外优化:统一WebClient的Jackson配置

若需FirstBackend调用SecondBackend时也遵循相同的Jackson规则(如snake_case命名、未知属性校验),可配置WebClient的消息转换器,替代手动序列化:

@Bean
public WebClient webClient(ObjectMapper objectMapper) {
    // 基于全局ObjectMapper扩展snake_case命名策略
    ObjectMapper webClientMapper = objectMapper.copy()
            .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);

    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(webClientMapper);

    return WebClient.builder()
            .defaultMessageConverters(converters -> {
                // 移除默认Jackson转换器,替换为自定义配置的
                converters.removeIf(c -> c instanceof MappingJackson2HttpMessageConverter);
                converters.add(converter);
            })
            .build();
}

修改SecondBackend调用代码,直接传递对象即可:

public Mono<String> searchInFullTextMode(FullTextSearchQuery fullTextSearchQuery, SearchEngine searchEngine) {
    return webClient.post().uri(searchEngine.getUrl() + "/fulltext")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON)
            .bodyValue(fullTextSearchQuery) // 直接传递对象,WebClient自动序列化
            .retrieve()
            .onStatus(HttpStatus::isError, WebClientHelper::handleDataResponseException)
            .bodyToMono(String.class);
}

内容的提问来源于stack exchange,提问作者Fethi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 00:13:13