SpringBoot:拦截器从请求中读取特定字段并在响应中进行设置

我们的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;

}

}

当我一次测试一次发送一个请求时,此方法有效。但是,是否可以保证在有多个请求时在同一线程中调用afterBodyReadbeforeBodyWrite

如果不是,或者什至不是,这样做的最佳方法是什么?

回答:

我认为您不需要自己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可以替换为

RequestContextHolder.getRequestAttributes().setAttribute(Common.class.getName(),common,RequestAttributes.SCOPE_REQUEST);

RequestContextHolder.getRequestAttributes().getAttribute(Common.class.getName(),RequestAttributes.SCOPE_REQUEST);

关于线程安全

1)我们基于标准的基于servlet的Spring

Web应用程序提供了每个请求线程的场景。工作线程之一通过所有筛选器和例程处理请求。处理链将从头到尾由相同的线程执行。因此,afterBodyReadbeforeBodyWrite保证由给定请求的同一线程执行。

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

回到顶部