不灭的焱

革命尚未成功,同志仍须努力下载JDK17

作者:Albert.Wen  添加时间:2021-05-25 13:55:08  修改时间:2023-02-01 11:20:11  分类:Java框架/系统  编辑

在Spring Boot中,为了随时都能取到当前请求的Request对象,可以通过RequestContextHolder的静态方法getRequestAttributes()获取Request相关的变量,如Request, Response等。

  • RequestContextHolder:持有上下文的Request容器;
  • 通过RequestContextHolder的静态方法可以随时随地取到当前请求的Request对象;
// 获取相关对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

if (requestAttributes != null) {
    // 底层实现:request.getAttribute("userId");
    String userId = (String) requestAttributes.getAttribute("userId", RequestAttributes.SCOPE_REQUEST);

    // 底层实现:session.getAttribute("userId");
    String userId2 = (String) requestAttributes.getAttribute("userId", RequestAttributes.SCOPE_SESSION);

    // 或者转成具体对象
    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
    HttpServletRequest request = servletRequestAttributes.getRequest();
    HttpServletResponse response = servletRequestAttributes.getResponse();
    HttpSession session = request.getSession();
}

 

 


 

Spring MVC 之RequestContextHolder分析

最近遇到的问题是在Service层获取Request和Response,正常来说在Service层是没有Request的,然而直接从Controlller传过来的话解决方法太粗暴,后来发现了SpringMVC提供的RequestContextHolder,遂去分析一番,并借此对SpringMVC的结构深入了解一下。

1.RequestContextHolder的使用

RequestContextHolder顾名思义,持有上下文的Request容器,使用是很简单的,具体使用如下:

// 两个方法在没有使用JSF的项目中是没有区别的
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
// RequestContextHolder.getRequestAttributes();

// 从session里面获取对应的值
String str = (String) requestAttributes.getAttribute("name", RequestAttributes.SCOPE_SESSION);

HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
HttpServletResponse response = ((ServletRequestAttributes)requestAttributes).getResponse();

看到这一般都会想到几个问题:

  1. request和response怎么和当前请求挂钩?
  2. request和response等是什么时候设置进去的?

2.解决疑问

2.1 request和response怎么和当前请求挂钩?

首先分析RequestContextHolder这个类,里面有两个ThreadLocal保存当前线程下的request,关于ThreadLocal可以参考我的另一篇博文[Java学习记录--ThreadLocal使用案例]

// 得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");

// 可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");

再看getRequestAttributes()方法,相当于直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request。

public static RequestAttributes getRequestAttributes() {
    RequestAttributes attributes = requestAttributesHolder.get();
    if (attributes == null) {
        attributes = inheritableRequestAttributesHolder.get();
    }
    return attributes;
}

2.2 request和response等是什么时候设置进去的?

找这个的话需要对SpringMVC结构的DispatcherServlet的结构有一定了解才能准确的定位该去哪里找相关代码。

在IDEA中会显示如下的继承关系:

左边1,这里是Servlet的接口和实现类。

右边2,这里是使得SpringMVC具有Spring的一些环境变量和Spring容器。类似的XXXAware接口就是对该类提供Spring感知,简单来说就是如果想使用Spring的XXXX就要实现XXXAware,Spring会把需要的东西传送过来。

那么剩下要分析的的就是三个类,简单看下源码

  1. HttpServletBean 进行初始化工作
  2. FrameworkServlet 初始化 WebApplicationContext,并提供service方法预处理请
  3. DispatcherServlet 具体分发处理

那么就可以在FrameworkServlet查看到该类重写了service(),doGet(),doPost()...等方法,这些实现里面都有一个预处理方法`processRequest(request, response);`,所以定位到了我们要找的位置

查看`processRequest(request, response);`的实现,具体可以分为三步:

  1. 获取上一个请求的参数
  2. 重新建立新的参数
  3. 设置到XXContextHolder
  4. 父类的service()处理请求
  5. 恢复request
  6. 发布事件
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    
    // 获取上一个请求保存的LocaleContext
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    
    // 建立新的LocaleContext
    LocaleContext localeContext = buildLocaleContext(request);
    
    // 获取上一个请求保存的RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    
    // 建立新的RequestAttributes
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    
    // 具体设置的方法
    initContextHolders(request, localeContext, requestAttributes);

    try {
        doService(request, response);
    }
    catch (ServletException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        // 恢复
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }

        if (logger.isDebugEnabled()) {
            if (failureCause != null) {
                this.logger.debug("Could not complete request", failureCause);
            }
            else {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    logger.debug("Leaving response open for concurrent processing");
                }
                else {
                    this.logger.debug("Successfully completed request");
                }
            }
        }
        
        // 发布事件
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

再看initContextHolders(request, localeContext, requestAttributes)方法,把新的RequestAttributes设置进LocalThread,实际上保存的类型为ServletRequestAttributes,这也是为什么在使用的时候可以把RequestAttributes强转为ServletRequestAttributes。

private void initContextHolders(
        HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {

    if (localeContext != null) {
        LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
    }
    if (requestAttributes != null) {
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
    }
    if (logger.isTraceEnabled()) {
        logger.trace("Bound request context to thread: " + request);
    }
}

因此RequestContextHolder里面最终保存的为ServletRequestAttributes,这个类相比RequestAttributes方法是多了很多。

 

 

摘自:https://blog.csdn.net/asdfsadfasdfsa/article/details/79158459