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

时间:2015-11-11 21:46:18

标签: java json spring rest generics

我有以下控制器:

@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

2 个答案:

答案 0 :(得分:1)

经过长时间的研究,我发现杰克逊的作品:

@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
    }

}

然后发送请求为{        “用户名”:XXXX,        “@class”: “包.....请求” }

引用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,将前端和后端分离。

请原谅我可怜的英语。

答案 1 :(得分:0)

What Spring really sees is:

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) {
    }
}

For returned objects it doesn't matter as Jackson will generate output proper JSON anyway but it looks like Spring can't handle incoming Object same way.

You might try replacing generics with just SomeSuperEntity and take a look at Spring @RequestBody containing a list of different types (but same interface)