Spring Rest Controllerによって処理されるすべての要求と応答には、特定の値を持つCommonセクションがあります。
{
"common": {
"requestId": "foo-bar-123",
"otherKey1": "value1",
"otherKey2": "value2",
"otherKey3": "value3"
},
...
}
現在、すべてのコントローラー関数はcommon
を読み取り、それを手動で応答にコピーしています。なんらかのインターセプターに移動したいと思います。
ControllerAdvice
とThreadLocal
を使用してこれを実行しようとしました:
@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つのリクエストの送信をテストするときに機能します。しかし、複数のリクエストが来たときに、afterBodyRead
とbeforeBodyWrite
が同じスレッドで呼び出されることが保証されていますか?
そうでない場合、またはそうでない場合でも、これを行う最良の方法は何ですか?
また、私はすでにこのスレッドに回答しましたが、私はそのような種類の問題を解決する別の方法を好みます
このシナリオでは、アスペクトを使用します。
これを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
エンリッチメントが発生するエンドポイントをマークします。