在使用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