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

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");
}

问题原因分析

  1. 转换器作用范围不匹配:自定义转换器针对LineString类型,但Spring处理@RequestBody RequestSaveRoute时,会优先用默认的MappingJackson2HttpMessageConverter反序列化整个对象,而非单独为path字段调用自定义转换器。HttpMessageConverter是处理整个请求体的类型,而非对象内部的单个字段。
  2. 转换器优先级问题:默认Jackson转换器优先级高于自定义转换器,会先被调用,导致自定义转换器无法触发。
  3. 类名不一致:自定义转换器类名为CustomHttpMessageConverter,但构造函数和配置中用了PointListHttpMessageConverter,属于笔误,虽不影响supports方法调用,但会导致代码逻辑混乱。

解决方案

方案一:使用Jackson自定义字段反序列化器(推荐)

针对对象内部字段的反序列化需求,使用Jackson的JsonDeserializer更合适:

  1. 创建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);
    }
}
  1. 在RequestSaveRoute的path字段上添加注解:
@Data
public class RequestSaveRoute {
    private String title;
    private String description;
    @JsonDeserialize(using = LineStringDeserializer.class)
    private LineString path;
}

方案二:调整HttpMessageConverter适配整个请求体

若坚持使用HttpMessageConverter,需修改为处理RequestSaveRoute类型,并调整优先级:

  1. 修改自定义转换器:
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 {
        // 实现序列化逻辑
    }
}
  1. 调整转换器顺序,放在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,提问作者배수아

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 21:54:56