JavaSpringBoot:从请求中读取特定字段并在响应中设置它的拦截器
我们的Spring Rest控制器处理的所有请求和响应都有一个公共部分,该部分具有某些值:
{
"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;
}
}
当我测试一次发送一个请求时,这就起作用了。但是,当出现多个请求时,是否保证在同一线程中调用afterBodyRead
和beforeBodyWrite
如果没有,或者甚至没有,那么最好的方法是什么
# 1 楼答案
快速回答:
RequestBodyAdvice
和ResponseBodyAdvice
在同一个线程中为一个请求调用可以在以下位置调试实现:^{}
但你这样做并不安全:
ThreadLocal
应该定义为static final
,否则它与任何其他类属性类似ResponseBodyAdvice
的调用(因此不会删除threadlocal数据)“更安全的方式”:在
afterBodyRead
方法中,使请求主体支持任何类(而不仅仅是MyGenericPojo
):ThreadLocal#remove
MyGenericPojo
,然后将公共数据设置为threadlocal# 2 楼答案
我认为不需要你自己的
ThreadLocal
你可以使用请求属性编辑
Optional
可以替换为编辑2
关于线程安全
1)标准的基于servlet的Spring web应用程序我们有每个请求线程的场景。请求由一个工作线程通过所有过滤器和例程进行处理。处理链将从头到尾由同一个线程执行。所以
afterBodyRead
和beforeBodyWrite
保证由同一个线程为给定的请求执行2)RequestResponseDevice本身是无状态的。我们使用了
RequestContextHolder.getRequestAttributes()
,它是ThreadLocal,并声明为ThreadLocal javadoc声明:
所以我没有看到任何线程安全问题
# 3 楼答案
如果只是从请求复制到响应的元数据,可以执行以下操作之一:
1-将元数据存储在请求/响应头中,只需使用过滤器进行复制:
}
2-将工作转移到服务层,在那里你可以通过一个可重用的通用方法来完成cope,或者让它通过AOP运行
# 4 楼答案
我也已经回答了这个问题,但我更喜欢用另一种方法来解决这类问题
在这个场景中,我会使用Aspect-s
我已经写了一个文件,但你应该创建适当的单独类
我们这里有一个注释
@EnrichWithCommon
,它标记了应该进行充实的端点