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
相关产品推荐
相关产品推荐

