![](/img/trans.png)
[英]How to read only a particular JSON node from MongoDb in SpringBoot
[英]SpringBoot: Interceptor to read particular field from request and set it in the response
我們的 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;
}
}
這在我測試一次發送一個請求時有效。 但是,當多個請求到來時,是否可以保證在同一個線程中調用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
s 可以替換為
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 並聲明為
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
和 ThreadLocal javadoc 指出:
他的類提供線程局部變量。 這些變量不同於它們的普通對應變量,因為每個訪問一個(通過其 get 或 set 方法)的線程都有自己的、獨立初始化的變量副本。
所以我沒有看到這個 sulotion 有任何線程安全問題。
快速回答: RequestBodyAdvice
和ResponseBodyAdvice
在同一線程內針對一個請求調用。
您可以在以下ServletInvocableHandlerMethod#invokeAndHandle
調試實現: ServletInvocableHandlerMethod#invokeAndHandle
但是,您這樣做的方式並不安全:
ThreadLocal
應該定義為static final
,否則它類似於任何其他類屬性ResponseBodyAdvice
調用(因此不會刪除線程本地數據) “更安全的方式”:在afterBodyRead
方法MyGenericPojo
請求正文支持任何類(不僅僅是MyGenericPojo
):
ThreadLocal#remove
MyGenericPojo
然后將公共數據設置為 threadlocal我也已經回答了這個線程,但我更喜歡另一種方法來解決此類問題
我會在這種情況下使用 Aspect-s。
我已經將它包含在一個文件中,但您應該創建適當的單獨類。
@Aspect
@Component
public class CommonEnricher {
// annotation to mark methods that should be intercepted
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EnrichWithCommon {
}
@Configuration
@EnableAspectJAutoProxy
public static class CommonEnricherConfig {}
// Around query to select methods annotiated with @EnrichWithCommon
@Around("@annotation(com.example.CommonEnricher.EnrichWithCommon)")
public Object enrich(ProceedingJoinPoint joinPoint) throws Throwable {
MyGenericPojo myGenericPojo = (MyGenericPojo) joinPoint.getArgs()[0];
var common = myGenericPojo.getCommon();
if (common.getRequestId() == null) {
common.setRequestId(UUID.randomUUID().toString());
}
//actual rest controller method invocation
MyGenericPojo res = (MyGenericPojo) joinPoint.proceed();
//adding common to body
res.setCommon(common);
return res;
}
//example controller
@RestController
@RequestMapping("/")
public static class MyRestController {
@PostMapping("/test" )
@EnrichWithCommon // mark method to intercept
public MyGenericPojo test(@RequestBody MyGenericPojo myGenericPojo) {
return myGenericPojo;
}
}
}
我們在這里有一個注釋@EnrichWithCommon
,它標記了應該發生富集的端點。
如果它只是從請求復制到響應的元數據,則可以執行以下操作之一:
1-將元存儲在請求/響應標頭中,只需使用過濾器進行復制:
@WebFilter(filterName="MetaDatatFilter", urlPatterns ={"/*"})
public class MyFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("metaData", httpServletRequest.getHeader("metaData"));
}
}
2- 將工作移至服務層,您可以在其中通過可重用的通用方法進行處理,或通過 AOP 運行
public void copyMetaData(whatEverType request,whatEverType response) {
response.setMeta(request.getMeta);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.