Java Spring通用REST控制器:解析请求主体
我有以下控制器:Java Spring通用REST控制器:解析请求主体,java,json,spring,rest,generics,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(@PathVar
@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) {
}
}
和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;
}
}
然后,在服务层上,我定义了接收到的实体(使用纯json)并将其转换为:
final EntityMetaData entityMetadata = getEntityMetadataByName(alias);
final T parsedEntity = getGlobalGson().fromJson(entity, entityMetadata.getEntityType());
其中,
EntityMetaData
是enum
,在实体别名和类之间定义了关系。别名为@PathVariable
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) {
}
}
公共类CrudController{
@RequestMapping(方法=GET)
公共Iterable findAll(@PathVariable字符串实体){
}
@RequestMapping(value=“{id}”,method=GET)
公共对象findOne(@PathVariable字符串实体,@PathVariable字符串id){
}
@请求映射(方法=POST)
公共无效保存(@PathVariable字符串实体,@RequestBody对象体){
}
}
对于返回的对象,这并不重要,因为Jackson将生成正确的JSON输出,但Spring似乎无法以相同的方式处理传入的对象
你可以试着用一些超级实体来代替泛型,看看经过长时间的研究,我发现它在Jackson中有效:
@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);
}
}
公共抽象类ApiPost{
抽象保护响应post(请求)抛出错误异常;
@应答器
@RequestMapping(method=RequestMethod.POST)
公众回应站(
@请求体请求请求
)抛出IOException{
返回此邮件(请求);
}
}
然后是控制器
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
}
}
公共类ExistApi{
公共最终静态字符串URL=“/user/exist”;
@吸气剂
@塞特
公共静态类请求实现ApiRequest{
私有字符串用户名;
}
}
@控制器
@请求映射(URL)
公共类ExistaPicController扩展了ApiPost{
@凌驾
受保护的响应post(请求)实现APIRest{
//做点什么
//和返回响应
}
}
然后将请求作为{
“用户名”:xxxx,
@class:“包…..请求”
}
参考文献a
但对我来说,最好的解决方案不是使用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);
}
}
公共抽象类ApiPost{
@自动连线
私有对象映射器映射器;
受保护类getClazz(){
返回(类)GenericTypeResolver
.resolveTypeArgument(getClass(),ApiPost.class);
}
抽象保护响应post(请求)抛出错误异常;
@应答器
@RequestMapping(method=RequestMethod.POST)
公众回应站(
@请求体请求请求
)抛出IOException{
//解决spring通用问题
REQUEST req=mapper.convertValue(REQUEST,getClazz());
返回此邮件(请求);
}
}
这样,我们就不需要请求json中的ApiRequest接口和@class,而是将前端和后端解耦
请原谅我的英语不好。Spring将为您的
CrudController
类型创建一个对象。没有其他类型信息,即SomeSuperEntity
子类型。您希望Spring(然后是Jackson)从何处获得它?基本上,不要像使用CrudController
那样。为您关心的每种类型创建一个专用的@Controller
类,并使用适当的映射。这很有趣。为什么?提供常规/animates
或/animates/1
端点来获取所有动物或ID=1的任何动物,无论是狗还是猫,有什么不对?你建议强迫人们提前知道他们是要狗还是要猫,对吗?像/动物/狗/1
。此外,如果您想获得所有动物,您需要迭代收集在一起的所有动物子类型/animals/dogs
,/animats/cats
等。我自己也在尝试解决类似的用例,并努力寻找最佳方法。@SotiriosDelimanolis目前我有约200个实体,我希望我不需要约200个控制器来执行CRUD操作。我知道存在类型擦除问题。因此,在我看来,作为一种解决方法,我可以将请求正文作为纯文本并手动解析(我可以在服务层中获取对象的类型),但我不确定这在性能和安全性方面是否是一个好主意。您解决过这个问题吗?您可能需要启用“defaultTyping”:在哪里使用T扩展APIRESQUEST?你能举个例子吗?
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
}
}
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);
}
}