java Spring 通用 REST 控制器:解析请求正文
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33660443/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring generic REST controller: parsing request body
提问by Oleg Gumennyj
I have following controller:
我有以下控制器:
@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
class looks like:
SomeSuperEntity
类看起来像:
public abstract class SomeSuperEntity extends AbstractEntity {
// some logic
}
And AbstractEntity
its abstract class with some field:
而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;
}
}
All subclasses of SomeSuperEntity
- simple JavaBeans.
In case with findAll()
and findOne(id)
methods - everything works fine.
I create entity in service layer and it returns to client as JSON with all fields that declared in subclass and in AbstractEntity
.
SomeSuperEntity
- 简单JavaBeans的所有子类。如果使用findAll()
和findOne(id)
方法 - 一切正常。我在服务层创建实体,它作为 JSON 返回给客户端,其中包含在子类和AbstractEntity
.
But when i tried to get request body in save(entity, body)
, i got following error:
但是当我尝试获取请求正文时save(entity, body)
,出现以下错误:
Could not read document: Can not construct instance of SomeSuperEntity, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
无法读取文档:无法构造 SomeSuperEntity 的实例,问题:抽象类型要么需要映射到具体类型,要么具有自定义反序列化器,要么使用附加类型信息进行实例化
If i remove abstract from SomeSuperEntity
, everything works, but i request body i got only those fields, that declared in AbstractEntity
.
如果我删除从抽象的SomeSuperEntity
,一切正常,但我要求的身体我只拿到了这些领域,在宣布的AbstractEntity
。
And here is my question, is there any workaround for such problems in my case?
If not, what would by the best solution here without any structure changes (making subcontloller for each entity is not an option)? Is retrieve body as plain text would be a good idea? Or it would be better to use Map
for this?
这是我的问题,在我的情况下是否有解决此类问题的方法?如果不是,那么这里最好的解决方案是什么而没有任何结构变化(为每个实体制作子控制器不是一种选择)?以纯文本形式检索正文是个好主意吗?或者最好为此使用Map
?
I'm using Spring v4.2.1 and Hymanson 2.6.3 as converter.
我使用 Spring v4.2.1 和 Hymanson 2.6.3 作为转换器。
There is a bit of information about generic controllers, but i couldn't find anything that cover my case. So, please, navigate in case of duplicate question.
有一些关于通用控制器的信息,但我找不到任何涵盖我的情况的信息。因此,请在出现重复问题时进行导航。
Thanks in advance.
提前致谢。
UPD: Currently its working as follow:
I add add additional check in my MessageConverter
and define @RequestBody
as String
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);
}
Then, on service layer, i define what entity received (in plain json) and convert it:
然后,在服务层,我定义接收到的实体(在纯 json 中)并转换它:
final EntityMetaData entityMetadata = getEntityMetadataByName(alias);
final T parsedEntity = getGlobalGson().fromJson(entity, entityMetadata.getEntityType());
Where EntityMetaData
is enum
with defined relations between entity alias and class. Alias comes as @PathVariable
.
哪里EntityMetaData
是enum
与实体别名和类之间的关系定义。别名作为@PathVariable
.
回答by ahll
For a long time research, I found that works in Hymanson:
经过长时间的研究,我发现在 Hymanson 中有效:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public interface ApiRequest {
}
and use
并使用
REQUEST extends ApiRequest
with this, do not change MessageConverter. Thus it will still require extra class info in your json request. For example, you could do:
有了这个,不要改变 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);
}
}
and then for controller
然后对于控制器
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
}
}
And then send request as { "username":xxxx, "@class":"package.....Request" }
然后将请求发送为 { "username":xxxx, "@class":"package.....Request" }
reference a https://github.com/FasterXML/Hymanson-docs/wiki/HymansonPolymorphicDeserialization
参考 https://github.com/FasterXML/Hymanson-docs/wiki/HymansonPolymorphicDeserialization
But for me, the best solution is not use spring to convert message, and left the abstract class do it.
但是对我来说,最好的解决方案是不要使用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);
}
}
with this, we do not require the ApiRequest interface and @class in request json, decouple the front and backend.
有了这个,我们不需要请求json中的ApiRequest接口和@class,前后端解耦。
Pardon my poor english.
原谅我可怜的英语。
回答by user158037
What Spring really sees is:
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) {
}
}
For returned objects it doesn't matter as Hymanson will generate output proper JSON anyway but it looks like Spring can't handle incoming Object same way.
对于返回的对象,这并不重要,因为Hyman逊无论如何都会生成输出正确的 JSON,但看起来 Spring 无法以相同的方式处理传入的对象。
You might try replacing generics with just SomeSuperEntity and take a look at Spring @RequestBody containing a list of different types (but same interface)
您可以尝试仅用 SomeSuperEntity 替换泛型,并查看包含不同类型列表(但接口相同)的 Spring @RequestBody