簡體   English   中英

如何根據 Spring MVC 控制器方法請求的 URI 為 @RequestBody 參數實例化特定的子類型?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM