• RequestContextHolder


    一、概述

    • RequestContextHolder:持有上下文的Request容器

    • 通过RequestContextHolder的静态方法可以随时随地取到当前请求的request对象

      // 获取相关对象
      RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

      // 底层实现:request.getAttribute(“userId”);
      String userId = (String) requestAttributes.getAttribute(“userId”,RequestAttributes.SCOPE_REQUEST);

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

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

    二、RequestContextHolder 源码

    public abstract class RequestContextHolder {
        private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
        /** 得到存储进去的request  */
        private static final ThreadLocal requestAttributesHolder = new NamedThreadLocal("Request attributes");
        /** 可被子线程继承的request */
        private static final ThreadLocal inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
    
        public RequestContextHolder() {
        }
    
        public static void resetRequestAttributes() {
            requestAttributesHolder.remove();
            inheritableRequestAttributesHolder.remove();
        }
    
        public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
            setRequestAttributes(attributes, false);
        }
    
        public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
            if (attributes == null) {
                resetRequestAttributes();
            } else if (inheritable) {
                inheritableRequestAttributesHolder.set(attributes);
                requestAttributesHolder.remove();
            } else {
                requestAttributesHolder.set(attributes);
                inheritableRequestAttributesHolder.remove();
            }
    
        }
    
    	//直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request.
        @Nullable
        public static RequestAttributes getRequestAttributes() {
            RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
            if (attributes == null) {
                attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
            }
    
            return attributes;
        }
    
        public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
            RequestAttributes attributes = getRequestAttributes();
            if (attributes == null) {
                if (jsfPresent) {
                    attributes = RequestContextHolder.FacesRequestAttributesFactory.getFacesRequestAttributes();
                }
    
                if (attributes == null) {
                    throw new IllegalStateException("No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
                }
            }
    
            return attributes;
        }
    
        private static class FacesRequestAttributesFactory {
            private FacesRequestAttributesFactory() {
            }
    
            @Nullable
            public static RequestAttributes getFacesRequestAttributes() {
                FacesContext facesContext = FacesContext.getCurrentInstance();
                return facesContext != null ? new FacesRequestAttributes(facesContext) : null;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    三、实现原理 ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景。
    在每个线程的内部存在一个数据结构为Map的ThreadLocals变量,以的形式保存着线程变量和其对应的值。
    对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
    ThreadLocal

    四、问题

    1. request和response怎么和当前请求挂钩

    getRequestAttributes():直接获取ThreadLocal里面的值,保证了每一次获取到的Request是该请求的request.

    2. request和response等是什么时候设置进去的

    DispatcherServlet继承关系:
    DispatcherServlet继承关系

    • 1:Servlet的接口和实现类.
    • 2:SpringMVC具有Spring的一些环境变量和Spring容器,类似的XXXAware接口就是对该类提供Spring感知,简单来说就是如果想使用Spring的XXXX就要实现XXXAware,spring会把需要的东西传送过来。
    • HttpServletBean:进行初始化工作
    • FrameworkServlet:初始化WebApplicationContext,并提供service方法预处理请求
    • DispatcherServlet:具体分发处理

    FrameworkServlet 类重写了service()、doGet()、doPost() 等方法,方法里面都有一个预处理方法 processRequest(request, response);
    查看 processRequest(request, response) 的实现:获取上一个请求的参数 重新建立新的参数 设置到XXContextHolder 父类的service()处理请求 恢复request 发布事件

    processRequest 源码
    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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    因此RequestContextHolder里面最终保存的为ServletRequestAttributes,这个类相比RequestAttributes方法是多了很多

    RequestContextHolder方法

    五、参考

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    数据库字段中使用逗号拼接,需要查询数据时
    【云原生】Docker网络模式详解
    【JavaSE】注释\标识符\关键字\字面常量\数据类型与变量
    Linux3._Linux环境基础开发工具使用
    linux中的kill 终止进程
    C语言计算一个数的 n 次方
    测试--自动化测试:关于unittest框架
    document.write()的用法-写入文本——修改样式、位置控制
    【第94题】JAVA高级技术-网络编程13(简易聊天室8:使用Socket传递图片)
    单分散亚微米聚苯乙烯—聚乙酸乙烯酯(P(St-VAc))聚合物微球/聚苯乙烯塑料微球聚乙烯醇相关知识
  • 原文地址:https://blog.csdn.net/m0_54853503/article/details/126114009