[英]Spring generic REST controller: parsing request body
我有以下控制器:
@RestController
@RequestMapping(value = "/{entity}", produces = MediaType.APPLICATION_JSON_VALUE)
public class CrudController<T extends SomeSuperEntity> {
@RequestMapping(method = GET)
public Iterable<T> findAll(@PathVariable String entity) {
}
@RequestMapping(value = "{id}", method = GET)
public T findOne(@PathVariable String entity, @PathVariable String id) {
}
@RequestMapping(method = POST)
public void save(@PathVariable String entity, @RequestBody T body) {
}
}
SomeSuperEntity
类看起来像:
public abstract class SomeSuperEntity extends AbstractEntity {
// some logic
}
和AbstractEntity
它的抽象类有一些字段:
public abstract class AbstractEntity implements Comparable<AbstractEntity>, Serializable {
private Timestamp firstField;
private String secondField;
public Timestamp getFirstField() {
return firstField;
}
public void setFirstField(Timestamp firstField) {
this.firstField = firstField;
}
public String getSecondField() {
return secondField;
}
public void setSecondField(String secondField) {
this.secondField = secondField;
}
}
SomeSuperEntity
所有子类 - 简单的JavaBeans。 如果使用findAll()
和findOne(id)
方法 - 一切正常。 我在服务层创建实体,它以JSON的形式返回客户端,所有字段都在子类和AbstractEntity
中声明。
但是当我试图在save(entity, body)
获取请求正文时,我得到以下错误:
无法读取文档:无法构造SomeSuperEntity的实例,问题:抽象类型需要映射到具体类型,具有自定义反序列化器,或者使用其他类型信息进行实例化
如果我从SomeSuperEntity
删除抽象,一切正常,但我请求正文我只得到那些在AbstractEntity
中声明的字段。
这是我的问题,在我的案例中是否有针对此类问题的解决方法? 如果没有,那么在没有任何结构变化的情况下,最佳解决方案会是什么(为每个实体制作子控制器不是一个选项)? 将检索体作为纯文本是个好主意吗? 或者为此使用Map
会更好吗?
我使用Spring v4.2.1和Jackson 2.6.3作为转换器。
有一些关于通用控制器的信息,但我找不到任何涵盖我的情况。 所以,请在重复问题的情况下导航。
提前致谢。
UPD :目前其工作方式如下:我在MessageConverter
添加了额外的检查,并将@RequestBody
定义为String
@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
if (IGenericController.class.isAssignableFrom(contextClass)) {
return CharStreams.toString(new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders())));
}
return super.read(type, contextClass, inputMessage);
}
然后,在服务层,我定义接收的实体(在普通的json中)并转换它:
final EntityMetaData entityMetadata = getEntityMetadataByName(alias);
final T parsedEntity = getGlobalGson().fromJson(entity, entityMetadata.getEntityType());
其中EntityMetaData
是enum
具有实体别名和类之间的已定义关系。 别名来自@PathVariable
。
经过长时间的研究,我发现杰克逊的作品:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public interface ApiRequest {
}
并使用
REQUEST extends ApiRequest
有了这个,不要改变MessageConverter。 因此,在json请求中仍然需要额外的类信息。 例如,您可以这样做:
public abstract class ApiPost<REQUEST extends ApiRequest > {
abstract protected Response post(REQUEST request) throws ErrorException;
@ResponseBody
@RequestMapping(method = RequestMethod.POST)
public Response post(
@RequestBody REQUEST request
) throws IOException {
return this.post(request);
}
}
然后是控制器
public class ExistApi {
public final static String URL = "/user/exist";
@Getter
@Setter
public static class Request implements ApiRequest{
private String username;
}
}
@Controller
@RequestMapping(URL)
public class ExistApiController extends ApiPost<Request> {
@Override
protected Response post(Request request) implements ApiRequest {
//do something
// and return response
}
}
然后发送请求为{“username”:xxxx,“@ class”:“package ..... Request”}
参考https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
但对我来说,最好的解决方案不是使用spring来转换消息,而是让抽象类做到了。
public abstract class ApiPost<REQUEST> {
@Autowired
private ObjectMapper mapper;
protected Class<REQUEST> getClazz() {
return (Class<REQUEST>) GenericTypeResolver
.resolveTypeArgument(getClass(), ApiPost.class);
}
abstract protected Response post(REQUEST request) throws ErrorException;
@ResponseBody
@RequestMapping(method = RequestMethod.POST)
public Response post(
@RequestBody REQUEST request
) throws IOException {
//resolve spring generic problem
REQUEST req = mapper.convertValue(request, getClazz());
return this.post(request);
}
}
有了这个,我们不要求请求json中的ApiRequest接口和@class,将前端和后端分离。
请原谅我可怜的英语。
Spring真正看到的是:
public class CrudController {
@RequestMapping(method = GET)
public Iterable<Object> findAll(@PathVariable String entity) {
}
@RequestMapping(value = "{id}", method = GET)
public Object findOne(@PathVariable String entity, @PathVariable String id) {
}
@RequestMapping(method = POST)
public void save(@PathVariable String entity, @RequestBody Object body) {
}
}
对于返回的对象,没关系,因为Jackson无论如何都会生成输出正确的JSON,但看起来Spring无法以相同的方式处理传入的Object。
您可以尝试使用SomeSuperEntity替换泛型,并查看包含不同类型列表的Spring @RequestBody(但是相同的接口)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.