繁体   English   中英

Spring通用REST控制器:解析请求体

[英]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());

其中EntityMetaDataenum具有实体别名和类之间的已定义关系。 别名来自@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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM