@SessionAttribute作用于处理器类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttribute设置的参数只用于暂时的传递,而不是长期的保存,长期保存的数据还是要放到Session中。

通过@SessionAttribute注解设置的参数有3类用法:

(1)在视图中通过request.getAttribute或session.getAttribute获取

(2)在后面请求返回的视图中通过session.getAttribute或者从model中获取

(3)自动将参数设置到后面请求所对应处理器的Model类型参数或者有@ModelAttribute注释的参数里面。

将一个参数设置到SessionAttribute中需要满足两个条件:

(1)在@SessionAttribute注解中设置了参数的名字或者类型

(2)在处理器中将参数设置到了model中。

@SessionAttribute用户后可以调用SessionStatus.setComplete来清除,这个方法只是清除SessionAttribute里的参数,而不会应用Session中的参数。

示例如下:注解@SessionAttribute中设置book、description和types={Double},这样值会被放到@SessionAttribute中,但Redirect跳转时就可以重新获得这些数据了,接下来操作sessionStatus.setComplete(),则会清除掉所有的数据,这样再次跳转时就无法获取数据了。

  1. @Controller
  2. @RequestMapping("/book")
  3. @SessionAttributes(value ={"book","description"},types={Double.class})
  4. public class RedirectController {
  5.  
  6. @RequestMapping("/index")
  7. public String index(Model model){
  8. model.addAttribute("book", "金刚经");
  9. model.addAttribute("description","不擦擦擦擦擦擦擦车");
  10. model.addAttribute("price", new Double("1000.00"));
  11. //跳转之前将数据保存到book、description和price中,因为注解@SessionAttribute中有这几个参数
  12. return "redirect:get.action";
  13. }
  14.  
  15. @RequestMapping("/get")
  16. public String get(@ModelAttribute ("book") String book,ModelMap model,
  17. SessionStatus sessionStatus){
  18. //可以获得book、description和price的参数
  19. System.out.println(model.get("book")+";"+model.get("description")+";"+model.get("price"));
  20. sessionStatus.setComplete();
  21. return "redirect:complete.action";
  22. }
  23.  
  24. @RequestMapping("/complete")
  25. public String complete(ModelMap modelMap){
  26. //已经被清除,无法获取book的值
  27. System.out.println(modelMap.get("book"));
  28. modelMap.addAttribute("book", "妹纸");
  29. return "sessionAttribute";
  30. }
  31.  
  32. }

接下来我们分析一下@SessionAttribute的实现机制

第一步:我们首先要获取注解@SessionAttribute的值的情况,在RequestMappingHandlerAdapter中的getModelFactory中处理。

  1. protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
  2. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  3.  
  4. //这里面对注解的@SessionAttribute的处理器做处理
  5. ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
  6.  
  7. ........
  8. //会对@SessionAttribute操作的值进行处理
  9. modelFactory.initModel(webRequest, mavContainer, invocableMethod);
  10. ........
  11.  
  12. return getModelAndView(mavContainer, modelFactory, webRequest);
  13. }

在getModelFactory中会创建@SessionAttribute的处理器SessionAttributeHandler

  1. private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
  2. //创建SessionAttribute处理器
  3. SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
  4.  
  5. return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
  6. }

getSessionAttributesHandler的操作就是获取或者初始化SessionAttributesHandler

  1. //已经获取过的SessionAttribute放到Map中,如果没有则需要初始化
  2. private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
  3. Class<?> handlerType = handlerMethod.getBeanType();
  4. SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
  5. if (sessionAttrHandler == null) {
  6. synchronized (this.sessionAttributesHandlerCache) {
  7. sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
  8. if (sessionAttrHandler == null) {
  9. //初始化sessionAttrHandler,并放到map中
  10. sessionAttrHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
  11. this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler);
  12. }
  13. }
  14. }
  15. return sessionAttrHandler;
  16. }

SessionAttributesHandler的构造函数中的操作如下,其实就是解析被@SessionAttribute注解的处理器,这样就完成了@SessionAttribute注解中设置的key的解析。

  1. public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
  2. Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
  3. this.sessionAttributeStore = sessionAttributeStore;
  4. //解析被@SessionAttribute注解的处理器
  5. SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
  6. if (annotation != null) {
  7. this.attributeNames.addAll(Arrays.asList(annotation.names()));
  8. this.attributeTypes.addAll(Arrays.asList(annotation.types()));
  9. }
  10.  
  11. for (String attributeName : this.attributeNames) {
  12. this.knownAttributeNames.add(attributeName);
  13. }
  14. }

接下来我们看看springMVC对@SessionAttribute的处理操作,在ModelFactory.initModel会对@SessionAttribute的注解进行处理操作。

  1. protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
  2. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  3.  
  4. //这里面对注解的@SessionAttribute的处理器做处理
  5. ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
  6.  
  7. ........
  8. //会对@SessionAttribute操作的值进行处理
  9. modelFactory.initModel(webRequest, mavContainer, invocableMethod);
  10. ........
  11.  
  12. return getModelAndView(mavContainer, modelFactory, webRequest);
  13. }

initModel其实做了两步操作,一是:获取上一次请求保存在SessionAttributeHandler中值,给这次请求的值用,二是将这次请求的处理结果可能会对上次的@SessionAttribute中的值进行改变的值进行保存给下一次请求使用。

  1. public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
  2. throws Exception {
  3. //获取所有的@SessionAttribute注解设置的key中值
  4. Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
  5. //将获取的值传递给下一个请求使用
  6. mavContainer.mergeAttributes(sessionAttributes);
  7.  
  8. invokeModelAttributeMethods(request, mavContainer);
  9.  
  10. //请求访问完之后将修改的值重新放到@SessionAttributeStore设置的key中
  11. for (String name : findSessionAttributeArguments(handlerMethod)) {
  12. if (!mavContainer.containsAttribute(name)) {
  13. Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
  14. if (value == null) {
  15. throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
  16. }
  17. mavContainer.addAttribute(name, value);
  18. }
  19. }
  20. }

sessionAttributesHandler.retrieveAttributes的操作是将request中值,按照注解@SessionAttribute中key取值处理,然后保存到attribute中,作为这次请求的传递值使用。

  1. public Map<String, Object> retrieveAttributes(WebRequest request) {
  2. Map<String, Object> attributes = new HashMap<String, Object>();
  3. //获取注解@SessionAttribute中设置的key
  4. for (String name : this.knownAttributeNames) {
  5. //如果设置的key有值则把它保存到attribute中,给跳转之后的请求使用
  6. Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
  7. if (value != null) {
  8. attributes.put(name, value);
  9. }
  10. }
  11. return attributes;
  12. }

这样就完成了将值保存在SessionAttributeHandler中,这样下一次请求过来时依然可以从SessionAttributeHandler中获取上次的结果,完成了类似Session的实现机制,但明显感觉和Session不一样,所有的请求其值是保存在一个同一个SessionAttributeHandler中。

SessionAttributeHandler其实维持了一个Map结构来存取数据,功能主要有解析@SessionAttribute注解,get和set相关值,源码如下:

  1. public class SessionAttributesHandler {
  2.  
  3. private final Set<String> attributeNames = new HashSet<String>();
  4.  
  5. private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();
  6.  
  7. private final Set<String> knownAttributeNames =
  8. Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(4));
  9.  
  10. private final SessionAttributeStore sessionAttributeStore;
  11.  
  12. //构造函数,解析@SessionAttribute注解,将其设置额key等信息保存
  13. public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
  14. Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
  15. this.sessionAttributeStore = sessionAttributeStore;
  16.  
  17. SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
  18. if (annotation != null) {
  19. this.attributeNames.addAll(Arrays.asList(annotation.names()));
  20. this.attributeTypes.addAll(Arrays.asList(annotation.types()));
  21. }
  22.  
  23. for (String attributeName : this.attributeNames) {
  24. this.knownAttributeNames.add(attributeName);
  25. }
  26. }
  27.  
  28. public boolean hasSessionAttributes() {
  29. return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
  30. }
  31.  
  32. //判断类型
  33. public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
  34. Assert.notNull(attributeName, "Attribute name must not be null");
  35. if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
  36. this.knownAttributeNames.add(attributeName);
  37. return true;
  38. }
  39. else {
  40. return false;
  41. }
  42. }
  43.  
  44. //保存
  45. public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
  46. for (String name : attributes.keySet()) {
  47. Object value = attributes.get(name);
  48. Class<?> attrType = (value != null) ? value.getClass() : null;
  49.  
  50. if (isHandlerSessionAttribute(name, attrType)) {
  51. this.sessionAttributeStore.storeAttribute(request, name, value);
  52. }
  53. }
  54. }
  55.  
  56. //获取
  57. public Map<String, Object> retrieveAttributes(WebRequest request) {
  58. Map<String, Object> attributes = new HashMap<String, Object>();
  59. for (String name : this.knownAttributeNames) {
  60. Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
  61. if (value != null) {
  62. attributes.put(name, value);
  63. }
  64. }
  65. return attributes;
  66. }
  67.  
  68. //清除所有内容
  69. public void cleanupAttributes(WebRequest request) {
  70. for (String attributeName : this.knownAttributeNames) {
  71. this.sessionAttributeStore.cleanupAttribute(request, attributeName);
  72. }
  73. }
  74.  
  75. //获取所有值
  76. Object retrieveAttribute(WebRequest request, String attributeName) {
  77. return this.sessionAttributeStore.retrieveAttribute(request, attributeName);
  78. }
  79.  
  80. }

springMVC源码分析--@SessionAttribute用法及原理解析SessionAttributesHandler和SessionAttributeStore的更多相关文章

  1. SpringMVC源码分析6:SpringMVC的视图解析原理

    title: SpringMVC源码分析6:SpringMVC的视图解析原理 date: 2018-06-07 11:03:19 tags: - SpringMVC categories: - 后端 ...

  2. springMVC源码分析--异常处理机制HandlerExceptionResolver执行原理(二)

    上一篇博客springMVC源码分析--异常处理机制HandlerExceptionResolver简单示例(一)中我们简单地实现了一个异常处理实例,接下来我们要介绍一下HandlerExceptio ...

  3. [心得体会]SpringMVC源码分析

    1. SpringMVC (1) springmvc 是什么? 前端控制器, 主要控制前端请求分配请求任务到service层获取数据后反馈到springmvc的view层进行包装返回给tomcat, ...

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

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

  5. springMVC源码分析--AbstractControllerUrlHandlerMapping(六)

    上一篇博客springMVC源码分析--AbstractDetectingUrlHandlerMapping(五)中我们介绍了AbstractDetectingUrlHandlerMapping,其定 ...

  6. springMVC源码分析--HandlerMapping(一)

    HandlerMapping的工作就是为每个请求找到合适的请求找到一个处理器handler,其实现机制简单来说就是维持了一个url到Controller关系的Map结构,其提供的实际功能也是根据req ...

  7. springMVC源码分析--动态样式ThemeResolver(二)

    在上一篇博客springMVC源码分析--动态样式ThemeResolver(一)中我们介绍了多样式ThemeResolver的使用方法,接下来我们对源码进行简单的分析一下. ThemeResolve ...

  8. springMVC源码分析--页面跳转RedirectView(三)

    之前两篇博客springMVC源码分析--视图View(一)和springMVC源码分析--视图AbstractView和InternalResourceView(二)中我们已经简单的介绍了View相 ...

  9. 框架-springmvc源码分析(一)

    框架-springmvc源码分析(一) 参考: http://www.cnblogs.com/heavenyes/p/3905844.html#a1 https://www.cnblogs.com/B ...

随机推荐

  1. 小技巧-C#文本快速删除空行

    查找:^\s*\n 替换空格 选择正则表达式

  2. SQL to Java code for Elasticsearch

    Elasticsearch虽然定位为Search Engine,但是因其可以持久化数据,很多时候,我们把Elasticsearch当成Database用,但是Elasticsearch不支持SQL,就 ...

  3. FTP下载文件

    linux命令方式下载 step1: >>ftp ip port 根据提示输入用户名 根据提示输入用户密码 >>cd 目录(重要:一定要进入文件所在的目录) >>g ...

  4. CentOS 7下Flannel安装与配置

    1. 安装前的准备 etcd 3.2.9 Docker 17.12.0-ce 三台机器10.100.97.236, 10.100.97.92, 10.100.97.81 etcd不同版本之间的差别还是 ...

  5. Javascript中获取浏览器类型和操作系统版本等客户端信息常用代码

    /** * @author hechen */ var gs = { /**获得屏幕宽度**/ ScreenWidth: function () { return window.screen.widt ...

  6. Apache 安装与配置(WIN10)

    本地坏境:windows 10 Pro 1709 Apache版本:httpd-2.4.32-Win64-VC15 Apache下载地址:https://www.apachelounge.com/do ...

  7. 关于redis主从|哨兵|集群模式

    关于redis主从.哨兵.集群的介绍网上很多,这里就不赘述了. 一.主从 通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重 ...

  8. xcode8的那些坑儿

    前几天手又贱,更新了xcode8....被几个坑玩坏了.最起码,字体改了,我现在还没有适应.下面列举了这两天遇到的问题 1.关于相册,照相,通讯录,麦克风的权限问题 xcode8打完包安装后,你会发现 ...

  9. [HNOI 2018]排列

    Description 题库链接 给定 \(n\) 个整数 \(a_1, a_2, \dots, a_n, 0 \le ai \le n\) ,以及 \(n\) 个整数 \(w_1, w_2, \do ...

  10. [TJOI 2016&HEOI 2016]求和

    Description 题库链接 求 \[f(n)=\sum_{i=0}^n\sum_{j=0}^i S(i,j)\times 2^j \times (j!)\] \(S(i, j)\) 表示第二类斯 ...