我们的Spring Rest Controller处理的所有请求和响应都有一个Common部分,该部分具有某些值:
{ "common": { "requestId": "foo-bar-123", "otherKey1": "value1", "otherKey2": "value2", "otherKey3": "value3" }, ... }
目前,我所有的控制器功能都在读取common并将其手动复制到响应中。我想将其移至某种拦截器中。
common
我试图使用ControllerAdvice和来做到这一点ThreadLocal:
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; } }
当我一次测试一次发送一个请求时,此方法有效。但是,是否可以保证在有多个请求时在同一线程中调用afterBodyRead和beforeBodyWrite?
afterBodyRead
beforeBodyWrite
如果不是,或者什至不是,这样做的最佳方法是什么?
我认为您不需要自己ThreadLocal就可以使用请求属性。
@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()); } Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .map(ServletRequestAttributes::getRequest) .ifPresent(request -> {request.setAttribute(Common.class.getName(), common);}); return body; } @Override public MyGenericPojo beforeBodyWrite( MyGenericPojo body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { Optional.ofNullable(RequestContextHolder.getRequestAttributes()) .map(rc -> rc.getAttribute(Common.class.getName(), RequestAttributes.SCOPE_REQUEST)) .ifPresent(o -> { Common common = (Common) o; body.setCommon(common); }); return body; }
编辑
Optional可以替换为
Optional
RequestContextHolder.getRequestAttributes().setAttribute(Common.class.getName(),common,RequestAttributes.SCOPE_REQUEST); RequestContextHolder.getRequestAttributes().getAttribute(Common.class.getName(),RequestAttributes.SCOPE_REQUEST);
编辑2
关于线程安全
1)我们基于标准的基于servlet的Spring Web应用程序提供了每个请求线程的场景。工作线程之一通过所有筛选器和例程处理请求。处理链将从头到尾由相同的线程执行。因此,afterBodyRead并beforeBodyWrite保证由给定请求的同一线程执行。
2)您的RequestResponseAdvice本身是无状态的。我们使用了RequestContextHolder.getRequestAttributes()ThreadLocal并声明为
RequestContextHolder.getRequestAttributes()
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<>("Request attributes");
ThreadLocal javadoc指出:
他的课提供了线程局部变量。这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。
因此,我没有在此支持中看到任何线程安全问题。