Spring MVC中@RequestBody、@ResponseBody如何接收Abstract或Interface类型的参数?

在使用Spring-MVC对外发布Rest接口时,在某些场景中,入参可能会希望是一个接口类型或者抽象类型。SpingMVC的序列化默认采用的是Jackson来实现入参与出参的序列化,在调用方传递一个json字符串时,如何将json字符串转换为具体的实现class呢?

如果不做任何处理?你可能会得到一个类似如下的异常:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.xxx.Task, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: java.io.PushbackInputStream@4e40388; line: 1, column: 2] (through reference chain: java.util.ArrayList[0])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:892)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:139)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:217)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:25)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3736)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2810)

那么应该如何处理呢?接下来我们以具体的示例来演示。

假设我们需要实现一个向后台添加打印任务的接口,任务的类型分为课程打印、成绩单打印。(在此我们不讨论接口设计的合理性,只讨论存技术的实现)。

首先我们定义一个任务接口:

public interface PrintTask extends Serializable{
    Type getType();

    enum Type{
        COURSE,
        SCORE
    }
}

接着我们定义两个具体的任务实现类:

@Data // 此注解来自`lombok`,具体作用请参考:http://projectlombok.org
public class CoursePrintTask implements PrintTask{
    private String course;

    public Type getType(){
        return PrintTask.Type.COURSE;
    }
}

@Data // 此注解来自`lombok`,具体作用请参考:http://projectlombok.org
public class ScorePrintTask implements PrintTask{
    private Double score;

    public Type getType(){
        return PrintTask.Type.SCORE;
    }
}

定义Rest接口:

@RestController
@RequestMapping("/task")
@Slf4j // 此注解来自`lombok`,具体作用请参考:http://projectlombok.org
public class TaskController{

    @RequestMapping(value = "/print", method = RequestMethod.POST)
    public Task print(@RequestBody Task task){
        log.debug("received task: {}", task);
        // TODO print ...

        return task; // 直接返回任务对象
    }
}

构造请求:

curl -XPOST http://localhost:8080/task/print -d '{"course": "english", "type": "SCORE"}'

不出意外,如上请求将会触发后端异常,且异常堆栈与文章开头所列类似。这是因为Jackson不知道该采用哪个子类来对{"course": "english", "type": "SCORE"}进行反序列化。

如何告诉Jackson采用何种类型进行序列化呢?要做的事情也特别简单,代码如下:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = CoursePrintTask.class, name = "COURSE"),
        @JsonSubTypes.Type(value = ScorePrintTask.class, name = "SCORE")
})
public interface PrintTask extends Serializable{
    Type getType();

    enum Type{
        COURSE,
        SCORE
    }
}

更多关于jackson的异常与解决方案,请参考:http://www.baeldung.com/jackson-exception


   转载规则


《Spring MVC中@RequestBody、@ResponseBody如何接收Abstract或Interface类型的参数?》 Angus_Lu 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
HornetQ引发的一次生产环境故障 HornetQ引发的一次生产环境故障
前段时间生产环境遇到一次故障,最终分析原因是HornetQ队列空间满造成。HornetQ是JBoss可能旗下的一款MQ产品,现已捐献给了Apache的ActiveMQ。下面将本次故障分析分享给大家,因涉及产品的信息安全问题,本文隐去了某些敏
2018-09-03 20:46:34
下一篇 
Redis有序集合(SortedSet)的POP实现方法 Redis有序集合(SortedSet)的POP实现方法
最近在项目中遇到一个场景需要使用分布式的优先级队列,第一反应就是通过redis的sortedset数据结构来实现。但是阅读其API发现其没有类似List的LPOP与RPOP指令,但是可以根据其提供的ZRANG、ZREVRANGE、ZREM组
2018-05-09 19:50:28
  目录