SpringBoot:拦截器从请求中读取特定字段并在响应中进行设置
我们的Spring Rest Controller处理的所有请求和响应都有一个Common部分,该部分具有某些值:
{ "common": {
"requestId": "foo-bar-123",
"otherKey1": "value1",
"otherKey2": "value2",
"otherKey3": "value3"
},
...
}
目前,我所有的控制器功能都在读取common
并将其手动复制到响应中。我想将其移至某种拦截器中。
我试图使用ControllerAdvice
和来做到这一点ThreadLocal
:
@ControllerAdvicepublic 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
?
如果不是,或者什至不是,这样做的最佳方法是什么?
回答:
我认为您不需要自己ThreadLocal
就可以使用请求属性。
@Overridepublic 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
可以替换为
RequestContextHolder.getRequestAttributes().setAttribute(Common.class.getName(),common,RequestAttributes.SCOPE_REQUEST);RequestContextHolder.getRequestAttributes().getAttribute(Common.class.getName(),RequestAttributes.SCOPE_REQUEST);
关于线程安全
1)我们基于标准的基于servlet的Spring
Web应用程序提供了每个请求线程的场景。工作线程之一通过所有筛选器和例程处理请求。处理链将从头到尾由相同的线程执行。因此,afterBodyRead
并beforeBodyWrite
保证由给定请求的同一线程执行。
2)您的RequestResponseAdvice本身是无状态的。我们使用了RequestContextHolder.getRequestAttributes()
ThreadLocal并声明为
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<>("Request attributes");
ThreadLocal javadoc指出:
他的课提供了线程局部变量。这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。
因此,我没有在此支持中看到任何线程安全问题。
以上是 SpringBoot:拦截器从请求中读取特定字段并在响应中进行设置 的全部内容, 来源链接: utcz.com/qa/434706.html