java - 如何根据 Spring MVC 控制器方法的请求 URI 为 @RequestBody 参数实例化特定子类型?
问题描述
给定以下基本域模型:
abstract class BaseData { ... }
class DataA extends BaseData { ... }
class DataB extends BaseData { ... }
因此,我想编写一个 Spring MVC 控制器端点...
@PostMapping(path="/{typeOfData}", ...)
ResponseEntity<Void> postData(@RequestBody BaseData baseData) { ... }
可以从路径中的 typeOfData 推断出所需的 baseData 的具体类型。
这使我可以使用一种方法来处理具有不同正文有效负载的多个 URL。我会为每个有效负载提供一个具体的类型,但我不想创建多个控制器方法,它们都做同样的事情(尽管每个都做的很少)。
我面临的挑战是如何“通知”反序列化过程,以便实例化正确的具体类型。
我可以想到两种方法来做到这一点。
首先使用自定义HttpMessageConverter
...
@Bean
HttpMessageConverter httpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
@Override
public Object read(final Type type, final Class<?> contextClass, final HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// TODO How can I set this dynamically ?
final Type subType = DataA.class;
return super.read(subType, contextClass, inputMessage);
}
};
}
...这给了我一个挑战来确定基于HttpInputMessage
. Filter
当 URL 对我可用时,我可以使用 a来设置自定义标头,或者我可以ThreadLocal
通过 a 使用 a 也设置Filter
。对我来说听起来都不理想。
我的第二种方法是再次使用 a Filter
,这次将传入的有效负载包装在一个外部对象中,然后以一种使杰克逊能够通过@JsonTypeInfo
. 目前,这可能是我的首选方法。
我已经调查过HandlerMethodArgumentResolver
了,但是如果我尝试注册一个自定义的,它会在之后注册,RequestResponseBodyMethodProcessor
并且该类具有优先权。
解决方案
嗯,所以在输入所有这些内容之后,我在发布问题之前快速检查了一些内容,RequestResponseBodyMethodProcessor
并找到了另一个探索途径,这很有效。
为简洁起见,请原谅@Configuration
// mash @RestController
- WebMvcConfigurer
up 和 public 字段。这对我有用,并完全实现了我想要的:
@Configuration
@RestController
@RequestMapping("/dummy")
public class DummyController implements WebMvcConfigurer {
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface BaseData {}
public static class AbstractBaseData {}
public static class DataA extends AbstractBaseData {
public String a;
}
public static class DataB extends AbstractBaseData {
public String b;
}
private final MappingJackson2HttpMessageConverter converter;
DummyController(final MappingJackson2HttpMessageConverter converter) {
this.converter = converter;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(
new RequestResponseBodyMethodProcessor(Collections.singletonList(converter)) {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(BaseData.class)
&& parameter.getParameterType() == AbstractBaseData.class;
}
@Override
protected <T> Object readWithMessageConverters(
NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
throws IOException, HttpMediaTypeNotSupportedException,
HttpMessageNotReadableException {
final String uri =
webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI();
return super.readWithMessageConverters(
webRequest, parameter, determineActualType(webRequest, uri));
}
private Type determineActualType(NativeWebRequest webRequest, String uri) {
if (uri.endsWith("data-a")) {
return DataA.class;
} else if (uri.endsWith("data-b")) {
return DataB.class;
}
throw new HttpMessageNotReadableException(
"Unable to determine actual type for request URI",
new ServletServerHttpRequest(
webRequest.getNativeRequest(HttpServletRequest.class)));
}
});
}
@PostMapping(
path = "/{type}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<? extends AbstractBaseData> post(@BaseData AbstractBaseData baseData) {
return ResponseEntity.ok(baseData);
}
}
关键是我停止使用@RequestBody
,因为那是阻止我覆盖内置行为的原因。通过使用@BaseData
,我得到一个HandlerMethodArgumentResolver
唯一支持参数的。
除此之外,这是一个组装已经完成我需要的两个对象的情况,因此使用该转换器自动装配 aMappingJackson2HttpMessageConverter
并实例化 a 。RequestResponseBodyMethodProcessor
然后选择正确的方法来覆盖,以便我可以控制在我可以访问 URI 的点上使用的参数类型。
快速测试。给定两个请求的以下有效负载...
{“a”:“A”,“b”:“B”}
POST http://localhost:8081/dummy/data-a
......给出......的回应
{“一”:“一”}
POST http://localhost:8081/dummy/data-b
......给出......的回应
{“乙”:“乙”}
在我们的实际示例中,这意味着我们将能够编写一个支持 POST / PUT 的方法。我们可能需要构建对象并配置验证 - 或者,如果我们使用我们正在研究的 OpenAPI 3.0,我们可以生成模型并验证,而无需编写任何进一步的代码......但这是一项单独的任务;)
推荐阅读
- c# - 无法在没有 EF 的情况下将 ApplicationUser 设置为外键正在创建新表
- python - 无法在 discord.py 中执行多个 on_message 事件
- java - 在 Mockito 3.5.x 中使用 PowerMock 中的 SuppressStaticInitializationFor
- javascript - next-routes server.js 失败:TypeError:routes.getRequestHandler 不是函数
- c# - c# HashTable 线程安全吗?
- python - Django rest:动态包含或排除序列化器类字段
- c# - C# 字典列表列表
- pyspark - 如何在 Spark 中级联未知数量的条件而不循环每个条件
- reactjs - 我可以使用泛型类型为 React 组件提供类型吗?
- google-cloud-platform - 缺乏对分层角色和组的支持