web-dev-qa-db-ja.com

Spring Boot:リクエストから特定のフィールドを読み取り、レスポンスに設定するインターセプター

Spring Rest Controllerによって処理されるすべての要求と応答には、特定の値を持つCommonセクションがあります。

{
    "common": {
        "requestId": "foo-bar-123",
        "otherKey1": "value1",
        "otherKey2": "value2",
        "otherKey3": "value3"
    },
    ...
}

現在、すべてのコントローラー関数はcommonを読み取り、それを手動で応答にコピーしています。なんらかのインターセプターに移動したいと思います。

ControllerAdviceThreadLocalを使用してこれを実行しようとしました:

@ControllerAdvice
public class RequestResponseAdvice extends RequestBodyAdviceAdapter
    implements ResponseBodyAdvice<MyGenericPojo> {

  private ThreadLocal<Common> commonThreadLocal = new ThreadLocal<>();

  /* Request */

  @Override
  public boolean supports(
      MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
    return MyGenericPojo.class.isAssignableFrom(methodParameter.getParameterType());
  }

  @Override
  public Object afterBodyRead(
      Object body,
      HttpInputMessage inputMessage,
      MethodParameter parameter,
      Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    var common = (MyGenericPojo)body.getCommon();
    if (common.getRequestId() == null) {
       common.setRequestId(generateNewRequestId()); 
    }
    commonThreadLocal(common);
    return body;
  }

  /* Response */

  @Override
  public boolean supports(
      MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    return MyGenericPojo.class.isAssignableFrom(returnType.getParameterType());
  }

  @Override
  public MyGenericPojo beforeBodyWrite(
      MyGenericPojo body,
      MethodParameter returnType,
      MediaType selectedContentType,
      Class<? extends HttpMessageConverter<?>> selectedConverterType,
      ServerHttpRequest request,
      ServerHttpResponse response) {
    body.setCommon(commonThreadLocal.get());
    commonThreadLocal.remove();
    return body;
  }
}

これは、一度に1つのリクエストの送信をテストするときに機能します。しかし、複数のリクエストが来たときに、afterBodyReadbeforeBodyWriteが同じスレッドで呼び出されることが保証されていますか?

そうでない場合、またはそうでない場合でも、これを行う最良の方法は何ですか?

5
daltonfury42

また、私はすでにこのスレッドに回答しましたが、私はそのような種類の問題を解決する別の方法を好みます

このシナリオでは、アスペクトを使用します。

これを1つのファイルに含めて記述しましたが、適切な個別のクラスを作成する必要があります。

@Aspect
@Component
public class CommonEnricher {

    // annotation to mark methods that should be intercepted
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface EnrichWithCommon {
    }


    @Configuration
    @EnableAspectJAutoProxy
    public static class CommonEnricherConfig {}

    // Around query to select methods annotiated with @EnrichWithCommon
    @Around("@annotation(com.example.CommonEnricher.EnrichWithCommon)")
    public Object enrich(ProceedingJoinPoint joinPoint) throws Throwable {
        MyGenericPojo myGenericPojo = (MyGenericPojo) joinPoint.getArgs()[0];

        var common = myGenericPojo.getCommon();
        if (common.getRequestId() == null) {
            common.setRequestId(UUID.randomUUID().toString());
        }

        //actual rest controller  method invocation
        MyGenericPojo res = (MyGenericPojo) joinPoint.proceed();

        //adding common to body
        res.setCommon(common);
        return res;
    }

    //example controller
    @RestController
    @RequestMapping("/")
    public static class MyRestController {

        @PostMapping("/test" )
        @EnrichWithCommon // mark method to intercept
        public MyGenericPojo test(@RequestBody  MyGenericPojo myGenericPojo) {
            return myGenericPojo;
        }
    }
}

ここに注釈@EnrichWithCommonエンリッチメントが発生するエンドポイントをマークします。

0
Nonika