dal*_*y42 5 java rest spring interceptor spring-boot
我们的 Spring Rest Controller 处理的所有请求和响应都有一个 Common 部分,该部分具有某些值:
{
"common": {
"requestId": "foo-bar-123",
"otherKey1": "value1",
"otherKey2": "value2",
"otherKey3": "value3"
},
...
}
Run Code Online (Sandbox Code Playgroud)
目前我所有的控制器功能都common在手动读取并将其复制到响应中。我想将它移动到某种拦截器中。
我尝试使用ControllerAdviceand来做到这一点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;
}
}
Run Code Online (Sandbox Code Playgroud)
这在我测试一次发送一个请求时有效。但是,它是保证afterBodyRead和beforeBodyWrite被称为在同一个线程,当多个请求来了?
如果没有,或者甚至没有,这样做的最佳方法是什么?
我认为没有必要ThreadLocal你自己使用request属性即可。
@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;
}
Run Code Online (Sandbox Code Playgroud)
编辑
Optionals 可以替换为
RequestContextHolder.getRequestAttributes().setAttribute(Common.class.getName(),common,RequestAttributes.SCOPE_REQUEST);
RequestContextHolder.getRequestAttributes().getAttribute(Common.class.getName(),RequestAttributes.SCOPE_REQUEST);
Run Code Online (Sandbox Code Playgroud)
编辑2
关于线程安全
1) 标准的基于 servlet 的 Spring Web 应用程序我们有每个请求一个线程的场景。请求由工作线程之一通过所有过滤器和例程进行处理。处理链从头到尾将由同一个线程执行。因此afterBodyRead并beforeBodyWrite保证对于给定的请求由同一线程执行。
2) 您的 RequestResponseAdvice 本身是无状态的。我们使用RequestContextHolder.getRequestAttributes()的是 ThreadLocal 并声明为
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
Run Code Online (Sandbox Code Playgroud)
ThreadLocal javadoc 指出:
他的类提供了线程局部变量。这些变量与其正常对应变量的不同之处在于,访问一个变量(通过其 get 或 set 方法)的每个线程都有其自己的、独立初始化的变量副本。
所以我在这个解决方案中没有看到任何线程安全问题。
| 归档时间: |
|
| 查看次数: |
1336 次 |
| 最近记录: |