Spring自定义HttpMessageConverter绑定LineString失效问题求助
问题排查:自定义HttpMessageConverter未生效导致LineString反序列化失败
问题背景
接收客户端请求时,需将请求体绑定到RequestSaveRoute类,该类包含org.locationtech.jts.geom.LineString类型的path字段。为此创建了CustomHttpMessageConverter,但转换器的readInternal方法未生效,触发JSON反序列化错误。
错误日志
2023-08-23 09:08:17.475 WARN 25216 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `org.locationtech.jts.geom.LineString` from Array value (token `JsonToken.START_ARRAY`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `org.locationtech.jts.geom.LineString` from Array value (token `JsonToken.START_ARRAY`)<EOL> at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 9] (through reference chain: com.example.core.route.dto.RequestSaveRoute["path"])] CustomHttpMessageConverter.support()
请求JSON示例
{ "title":"First Route", "path":[[127.01202, 36.3030], ...] }
注:原示例中path子数组格式有误,修正为每个子数组包含经纬度两个值
实体类代码
import org.locationtech.jts.geom.LineString; @Data public class RequestSaveRoute { private String title; private String description; private LineString path; }
自定义HttpMessageConverter代码
@Component public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<LineString> { public PointListHttpMessageConverter() { super(MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_JSON); } @Override protected boolean supports(Class<?> clazz) { System.out.println("CustomHttpMessageConverter.support()"); return LineString.class.isAssignableFrom(clazz); } @Override protected LineString readInternal(Class<? extends LineString> clazz, HttpInputMessage inputMessage) throws IOException { System.out.println("PointListHttpMessageConverter.readInternal"); ObjectMapper mapper = new ObjectMapper(); double[][] coordinates = mapper.readValue(inputMessage.getBody(), double[][].class); GeometryFactory gf = new GeometryFactory(); Coordinate[] madeCoordinates = Arrays.stream(coordinates).map(e -> new Coordinate(e[0], e[1]) ).toArray(Coordinate[]::new); return gf.createLineString(madeCoordinates); } @Override protected void writeInternal(LineString points, HttpOutputMessage outputMessage) throws IOException { // Implementation omitted for brevity. } }
Web配置与控制器代码
// 在Web Config中 @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { WebMvcConfigurer.super.extendMessageConverters(converters); converters.add(new PointListHttpMessageConverter()); } // 在Controller中 @PostMapping(consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE}) public ResponseEntity<?> saveRoute( @RequestBody RequestSaveRoute request ) { return ResponseEntity.ok("success"); }
问题原因分析
- 转换器作用范围不匹配:自定义转换器针对
LineString类型,但Spring处理@RequestBody RequestSaveRoute时,会优先用默认的MappingJackson2HttpMessageConverter反序列化整个对象,而非单独为path字段调用自定义转换器。HttpMessageConverter是处理整个请求体的类型,而非对象内部的单个字段。 - 转换器优先级问题:默认Jackson转换器优先级高于自定义转换器,会先被调用,导致自定义转换器无法触发。
- 类名不一致:自定义转换器类名为
CustomHttpMessageConverter,但构造函数和配置中用了PointListHttpMessageConverter,属于笔误,虽不影响supports方法调用,但会导致代码逻辑混乱。
解决方案
方案一:使用Jackson自定义字段反序列化器(推荐)
针对对象内部字段的反序列化需求,使用Jackson的JsonDeserializer更合适:
- 创建
LineString反序列化器:
public class LineStringDeserializer extends JsonDeserializer<LineString> { @Override public LineString deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { double[][] coordinates = p.readValueAs(double[][].class); GeometryFactory gf = new GeometryFactory(); Coordinate[] madeCoordinates = Arrays.stream(coordinates) .map(e -> new Coordinate(e[0], e[1])) .toArray(Coordinate[]::new); return gf.createLineString(madeCoordinates); } }
- 在
RequestSaveRoute的path字段上添加注解:
@Data public class RequestSaveRoute { private String title; private String description; @JsonDeserialize(using = LineStringDeserializer.class) private LineString path; }
方案二:调整HttpMessageConverter适配整个请求体
若坚持使用HttpMessageConverter,需修改为处理RequestSaveRoute类型,并调整优先级:
- 修改自定义转换器:
public class RequestSaveRouteHttpMessageConverter extends AbstractHttpMessageConverter<RequestSaveRoute> { private ObjectMapper objectMapper = new ObjectMapper(); private GeometryFactory gf = new GeometryFactory(); public RequestSaveRouteHttpMessageConverter() { super(MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_JSON); } @Override protected boolean supports(Class<?> clazz) { return RequestSaveRoute.class.isAssignableFrom(clazz); } @Override protected RequestSaveRoute readInternal(Class<? extends RequestSaveRoute> clazz, HttpInputMessage inputMessage) throws IOException { JsonNode root = objectMapper.readTree(inputMessage.getBody()); RequestSaveRoute route = new RequestSaveRoute(); route.setTitle(root.get("title").asText()); if (root.has("description")) { route.setDescription(root.get("description").asText()); } // 处理path字段 ArrayNode pathNode = (ArrayNode) root.get("path"); List<Coordinate> coordinates = new ArrayList<>(); for (JsonNode node : pathNode) { double lon = node.get(0).asDouble(); double lat = node.get(1).asDouble(); coordinates.add(new Coordinate(lon, lat)); } route.setPath(gf.createLineString(coordinates.toArray(new Coordinate[0]))); return route; } @Override protected void writeInternal(RequestSaveRoute route, HttpOutputMessage outputMessage) throws IOException { // 实现序列化逻辑 } }
- 调整转换器顺序,放在Jackson转换器之前:
@Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter); converters.add(new RequestSaveRouteHttpMessageConverter()); converters.add(new MappingJackson2HttpMessageConverter()); }
方案三:修复类名笔误
统一自定义转换器的类名和构造函数、配置中的引用:
@Component public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<LineString> { public CustomHttpMessageConverter() { super(MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_JSON); } // 其他代码不变 }
配置中修改为:
converters.add(new CustomHttpMessageConverter());
注:此方案仅修复类名错误,无法解决字段级反序列化问题
内容的提问来源于stack exchange,提问作者배수아
相关产品推荐
相关产品推荐

