SpringBoot:拦截器从请求中读取特定字段并在响应中进行设置 [英] SpringBoot: Interceptor to read particular field from request and set it in the response

查看:1339
本文介绍了SpringBoot:拦截器从请求中读取特定字段并在响应中进行设置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们的Spring Rest Controller处理的所有请求和响应都有一个Common部分,其中包含某些值:

All requests and responses handled by our Spring Rest Controller has a Common section which has certain values:

{
    "common": {
        "requestId": "foo-bar-123",
        "otherKey1": "value1",
        "otherKey2": "value2",
        "otherKey3": "value3"
    },
    ...
}

当前,我所有的控制器功能都在读取common并将其手动复制到响应中.我想将其移动到某种拦截器中.

Currently all my controller functions are reading the common and copying it into the response manually. I would like to move it into an interceptor of some sort.

我尝试使用ControllerAdviceThreadLocal来做到这一点:

I tried to do this using ControllerAdvice and 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?

This works when I test sending one request at a time. But, is it guaranteed that afterBodyRead and beforeBodyWrite is called in the same thread, when multiple requests are coming?

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

If not, or even otherwise, what is the best way of doing this?

推荐答案

我认为不需要您自己的ThreadLocal即可使用请求属性.

I think that there is no need of your own ThreadLocal you can use request attributes.

@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应用程序,我们有每个请求线程的情况.工作线程之一通过所有筛选器和例程处理请求.处理链将由相同的线程从头到尾执行.因此,对于给定的请求,afterBodyReadbeforeBodyWrite保证由同一线程执行.

1) standard servlet-based Spring web application we have thread-per-request scenario. Request is processed by one of the worker threads through all the filters and routines. The processing chain will be executed by the very same thread from start to end . So afterBodyRead and beforeBodyWrite guaranteed to be executed by the very same thread for a given request.

2)您的RequestResponseAdvice本身是无状态的.我们使用了RequestContextHolder.getRequestAttributes(),它是ThreadLocal并声明为

2) Your RequestResponseAdvice by itself is stateless. We used RequestContextHolder.getRequestAttributes() which is ThreadLocal and declared as

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
        new NamedThreadLocal<>("Request attributes");

并且ThreadLocal javadoc状态:

And ThreadLocal javadoc states:

他的类提供线程局部变量.这些变量不同于 他们的正常对手是,每个线程访问一个(通过 其get或set方法)具有其自己的,独立初始化的副本 变量.

his class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.

所以我看不到该支持中有任何线程安全问题.

So I don't see any thread-safety issues into this sulotion.

这篇关于SpringBoot:拦截器从请求中读取特定字段并在响应中进行设置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆