最近遇到的问题是在service获取request和response,正常来说在service层是没有request的,然而直接从controlller传过来的话解决方法太粗暴,后来发现了SpringMVC提供的RequestContextHolder遂去分析一番,并借此对SpringMVC的结构深入了解一下,后面会再发文章详细分析源码

1.RequestContextHolder的使用

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

  1. //两个方法在没有使用JSF的项目中是没有区别的
  2. RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
  3. //RequestContextHolder.getRequestAttributes();
  4. //从session里面获取对应的值
  5. String str = (String) requestAttributes.getAttribute("name",RequestAttributes.SCOPE_SESSION);
  6.  
  7. HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
  8. HttpServletResponse response = ((ServletRequestAttributes)requestAttributes).getResponse();

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

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

2.解决疑问

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

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

  1. //得到存储进去的request
  2. private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
  3. new NamedThreadLocal<RequestAttributes>("Request attributes");
  4. //可被子线程继承的request
  5. private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
  6. new NamedInheritableThreadLocal<RequestAttributes>("Request context");

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

  1. public static RequestAttributes getRequestAttributes() {
  2. RequestAttributes attributes = requestAttributesHolder.get();
  3. if (attributes == null) {
  4. attributes = inheritableRequestAttributesHolder.get();
  5. }
  6. return attributes;
  7. }

2.2request和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. 发布事
  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
  2. throws ServletException, IOException {
  3. long startTime = System.currentTimeMillis();
  4. Throwable failureCause = null;
  5. //获取上一个请求保存的LocaleContext
  6. LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
  7. //建立新的LocaleContext
  8. LocaleContext localeContext = buildLocaleContext(request);
  9. //获取上一个请求保存的RequestAttributes
  10. RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
  11. //建立新的RequestAttributes
  12. ServletRequestAttributes requestAttributes = buildRequestAttributes(request,
  13. response, previousAttributes);
  14. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  15. asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(),
  16. new RequestBindingInterceptor());
  17. //具体设置的方法
  18. initContextHolders(request, localeContext, requestAttributes);
  19. try {
  20. doService(request, response);
  21. }
  22. catch (ServletException ex) {
  23. failureCause = ex;
  24. throw ex;
  25. }
  26. catch (IOException ex) {
  27. failureCause = ex;
  28. throw ex;
  29. }
  30. catch (Throwable ex) {
  31. failureCause = ex;
  32. throw new NestedServletException("Request processing failed", ex);
  33. }
  34. finally {
  35. //恢复
  36. resetContextHolders(request, previousLocaleContext, previousAttributes);
  37. if (requestAttributes != null) {
  38. requestAttributes.requestCompleted();
  39. }
  40. if (logger.isDebugEnabled()) {
  41. if (failureCause != null) {
  42. this.logger.debug("Could not complete request", failureCause);
  43. }
  44. else {
  45. if (asyncManager.isConcurrentHandlingStarted()) {
  46. logger.debug("Leaving response open for concurrent processing");
  47. }
  48. else {
  49. this.logger.debug("Successfully completed request");
  50. }
  51. }
  52. }
  53. //发布事件
  54. publishRequestHandledEvent(request, response, startTime, failureCause);
  55. }
  56. }

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

  1. private void initContextHolders(HttpServletRequest request,
  2. LocaleContext localeContext,
  3. RequestAttributes requestAttributes) {
  4. if (localeContext != null) {
  5. LocaleContextHolder.setLocaleContext(localeContext,
  6. this.threadContextInheritable);
  7. }
  8. if (requestAttributes != null) {
  9. RequestContextHolder.setRequestAttributes(requestAttributes,
  10. this.threadContextInheritable);
  11. }
  12. if (logger.isTraceEnabled()) {
  13. logger.trace("Bound request context to thread: " + request);
  14. }
  15. }

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

项目示例可以参考:

SSM框架整合: nl101531/JavaWEB

SpringMVC之RequestContextHolder分析的更多相关文章

  1. SpringMVC之RequestContextHolder分析(转)

    链接:https://blog.csdn.net/zzy7075/article/details/53559902

  2. springMVC源码分析--DispatcherServlet请求获取及处理

    在之前的博客springMVC源码分析--容器初始化(二)DispatcherServlet中我们介绍过DispatcherServlet,是在容器初始化过程中出现的,我们之前也说过Dispatche ...

  3. SpringMVC源码分析--容器初始化(四)FrameworkServlet

    在上一篇博客SpringMVC源码分析--容器初始化(三)HttpServletBean我们介绍了HttpServletBean的init函数,其主要作用是初始化了一下SpringMVC配置文件的地址 ...

  4. SpringMVC源码分析(3)DispatcherServlet的请求处理流程

    <springmvc源码分析(2)dispatcherservlet的初始化>初始化DispatcherServlet的多个组件. 本文继续分析DispatcherServlet解析请求的 ...

  5. 8、SpringMVC源码分析(3):分析ModelAndView的形成过程

    首先,我们还是从DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response) throw ...

  6. 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

    从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...

  7. SpringMVC的流程分析(一)—— 整体流程概括

    SpringMVC的整体概括 之前也写过springmvc的流程分析,只是当时理解的还不透彻所以那篇文章就放弃了,现在比之前好了些,想着写下来分享下,也能增强记忆,也希望可以帮助到人,如果文章中有什么 ...

  8. springMVC源码分析--ViewNameMethodReturnValueHandler返回值处理器(三)

    之前两篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)和springMVC源码分析--HandlerMethodReturnValu ...

  9. springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)

    在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ...

随机推荐

  1. 关于inode&硬连接

    这两天看了一道面试题,什么是inode?我勒个去,第一次听说.于是Google了一下,发现下面这段内容讲解的非常不错,供大家参考. 一.inode是什么? 理解inode,要从文件储存说起.文件储存在 ...

  2. JavaScript公共函数

    [在此处输入文章标题] // JScript 文件 /* ================================================================== JS 公 ...

  3. 跟 Google 学 machineLearning [1] -- hello sklearn

    时至今日,我才发现 machineLearning 的应用门槛已经被降到了这么低,简直唾手可得.我实在找不到任何理由不对它进入深入了解.如标题,感谢 Google 为这项技术发展作出的贡献.当然,可能 ...

  4. ES6学习笔记四:Proxy与Reflect

    一:Proxy 代理. ES6把代理模式做成了一个类,直接传入被代理对象.代理函数,即可创建一个代理对象,然后我们使用代理对象进行方法调用,即可调用被包装过的方法: 1)创建 var proxy = ...

  5. V-rep学习笔记:main script and child scripts

    The main and child scripts The main script and the child scripts, which are simulation scripts, play ...

  6. 转 通过phpize为php在不重新编译php情况下安装模块openssl

    假定:php编译安装路径:/usr/local/php/apache编译安装路径:/usr/local/apache/php配置文件路径:/etc/php.iniphp安装源路径:/usr/sourc ...

  7. Windows在cmd杀掉进程

    问题描述: 在windows根据pid杀进程 问题解决: tasklist查看当前系统中的进程列表,然后针对你要杀的进程使用taskkill命令 #根据服务名taskkill /im nginx.ex ...

  8. Linux-配置虚拟IP

    Linux下配置网卡ip别名何谓ip别名?用windows的话说,就是为一个网卡配置多个ip.什么场合增加ip别名能派上用场?布网需要.多ip访问测试.特定软件对多ip的需要...and so on. ...

  9. 安全删除linux旧内核的方法

    我们在用yum升级系统之后,希望往往会为我们保持旧的内核文件,这样以防在出现硬件或者软件冲突的时候我们能够返回到旧的内核文件继续使用,如果我们想要安全的删除旧的内核文件,可以follow下面的方法. ...

  10. oracle的decode函数

    以下转自百度, 类似于case... when... then...else...end... DECODE函数是ORACLE PL/SQL是功能强大的函数之一,目前还只有ORACLE公司的SQL提供 ...