![](/img/trans.png)
[英]why can't I call a subclass method using a reference of a parent type that refers to an instance of a sub-type?
[英]How can I instantiate a specific sub-type for a @RequestBody parameter based on the requested URI for a Spring MVC controller method?
給定以下基本域模型:
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
確定 subType 的挑戰。 當 URL 對我可用時,我可能可以使用Filter
提前設置自定義標頭,或者我可以使用也通過Filter
設置的ThreadLocal
。 對我來說這聽起來都不理想。
我的第二種方法是再次使用Filter
,這次將傳入的有效負載包裝在一個外部對象中,然后以一種使傑克遜能夠通過@JsonTypeInfo
完成工作的方式提供類型。 目前這可能是我的首選方法。
我已經調查了HandlerMethodArgumentResolver
但如果我嘗試注冊一個自定義的,它是在RequestResponseBodyMethodProcessor
之后注冊的,並且該類具有優先權。
嗯,所以在輸入所有這些之后,我在發布問題之前快速檢查了RequestResponseBodyMethodProcessor
的某些內容,並找到了另一個探索的途徑,它工作得很好。
請原諒@Configuration
/ @RestController
/ WebMvcConfigurer
混搭和公共字段,所有這些都是為了簡潔起見。 以下是對我有用並完全達到我想要的效果:
@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
。
除此之外,這是組裝已經完成我需要的兩個對象的情況,因此自動裝配MappingJackson2HttpMessageConverter
並使用該轉換器實例化RequestResponseBodyMethodProcessor
。 然后選擇正確的方法來覆蓋,這樣我就可以控制在我有權訪問 URI 時使用的參數類型。
快速測試。 給定以下兩個請求的有效負載...
{ "a": "A", "b": "B" }
POST http://localhost:8081/dummy/data-a
......給出了......的回應
{ "a": "A" }
POST http://localhost:8081/dummy/data-b
......給出了......的回應
{“B”:“B”}
在我們的實際示例中,這意味着我們將能夠分別編寫一種支持 POST / PUT 的方法。 我們可能需要構建對象並配置驗證 - 或者,如果我們使用我們正在研究的 OpenAPI 3.0,我們可以生成模型並進行驗證而無需編寫任何進一步的代碼......但這是一項單獨的任務;)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.