前言
该注解顾名思义,作用是将Model中的属性同步到session会话当中,方便在下一次请求中使用(比如重定向场景~)。
虽然说Session的概念在当下前后端完全分离的场景中已经变得越来越弱化了,但是若为web开发者来说,我仍旧强烈不建议各位扔掉这个知识点,so我自然就建议大家能够熟练使用@SessionAttributes来简化平时的开发,本文带你入坑~
@SessionAttributes
我把这行字放在最前面:本文讲解的是org.springframework.web.bind.annotation.SessionAttributes而非org.springframework.web.bind.annotation.SessionAttribute,它两可完全不是一个概念哦~
关于@SessionAttribute的使用在这里
这个注解只能标注在类上,用于在多个请求之间传递参数,类似于Session的Attribute。
但不完全一样:一般来说@SessionAttributes设置的参数只用于暂时的传递,而不是长期的保存,长期保存的数据还是要放到Session中。(比如重定向之间暂时传值,用这个注解就很方便)
官方解释:当用@SessionAttributes标注的Controller向其模型Model添加属性时,将根据该注解指定的名称/类型检查这些属性,若匹配上了就顺带也会放进Session里。匹配上的将一直放在Sesson中,直到你调用了SessionStatus.setComplete()方法就消失了~~~
// @since 2.5 它只能标注在类上 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface SessionAttributes { // 只有名称匹配上了的 Model上的属性会向session里放置一份~~~ @AliasFor("names") String[] value() default {}; @AliasFor("value") String[] names() default {}; // 也可以拿类型来约束 Class<?>[] types() default {}; }
注意理解这句话:用户可以调用SessionStatus.setComplete来清除,这个方法只是清除@SessionAttributes里的参数,而不会应用于Session中的参数。也就是说使用API自己放进Session内和使用@SessionAttributes注解放进去还是有些许差异的~
Demo Show
下面用一个比较简单的例子演示一下@SessionAttributes它的作用:
@Controller @RequestMapping("/sessionattr/demo") @SessionAttributes(value = {"book", "description"}, types = {Double.class}) public class RedirectController { @RequestMapping("/index") public String index(Model model, HttpSession httpSession) { model.addAttribute("book", "天龙八部"); model.addAttribute("description", "我乔峰是个契丹人"); model.addAttribute("price", new Double("1000.00")); // 通过Sesson API手动放一个进去 httpSession.setAttribute("hero", "fsx"); //跳转之前将数据保存到Model中,因为注解@SessionAttributes中有,所以book和description应该都会保存到SessionAttributes里(注意:不是session里) return "redirect:get"; } // 关于@ModelAttribute 下文会讲 @RequestMapping("/get") public String get(@ModelAttribute("book") String book, ModelMap model, HttpSession httpSession, SessionStatus sessionStatus) { //可以从model中获得book、description和price的参数 System.out.println(model.get("book") + ";" + model.get("description") + ";" + model.get("price")); // 从sesson中也能拿到值 System.out.println(httpSession.getAttribute("book")); System.out.println("API方式手动放进去的:" + httpSession.getAttribute("hero")); // 使用@ModelAttribute也能拿到值 System.out.println(book); // 手动清除SessionAttributes sessionStatus.setComplete(); return "redirect:complete"; } @RequestMapping("/complete") @ResponseBody public String complete(ModelMap modelMap, HttpSession httpSession) { //已经被清除,无法获取book的值 System.out.println(modelMap.get("book")); System.out.println("API方式手动放进去的:" + httpSession.getAttribute("hero")); return "sessionAttributes"; } }
我们只需要访问入口请求/index就可以直接看到控制台输出如下:
天龙八部;我乔峰是个契丹人;1000.0 天龙八部 API方式手动放进去的:fsx 天龙八部 null API方式手动放进去的:fsx
浏览器如下图:
初识的小伙伴可以认真的观察本例,它佐证了我上面说的理论知识。
@SessionAttributes注解设置的参数有3类方式去使用它:
- 在视图view中(比如jsp页面等)通过request.getAttribute()或session.getAttribute获取
- 在后面请求返回的视图view中通过session.getAttribute或者从model中获取(这个也比较常用)
- 自动将参数设置到后面请求所对应处理器的Model类型参数或者有@ModelAttribute注释的参数里面(结合@ModelAttribute一起使用应该是我们重点关注的)
通过示例知道了它的基本使用,下面从原理层面去分析它的执行过程,实现真正的掌握它。
SessionAttributesHandler
见名之意,它是@SessionAttributes处理器,也就是解析这个注解的核心。管理通过@SessionAttributes标注了的特定会话属性,存储最终是委托了SessionAttributeStore来实现。
// @since 3.1 public class SessionAttributesHandler { private final Set<String> attributeNames = new HashSet<>(); private final Set<Class<?>> attributeTypes = new HashSet<>(); // 注意这个重要性:它是注解方式放入session和API方式放入session的关键(它只会记录注解方式放进去的session属性~~) private final Set<String> knownAttributeNames = Collections.newSetFromMap(new ConcurrentHashMap<>(4)); // sessonAttr存储器:它最终存储到的是WebRequest的session域里面去(对httpSession是进行了包装的) // 因为有WebRequest的处理,所以达到我们上面看到的效果。complete只会清楚注解放进去的,并不清除API放进去的~~~ // 它的唯一实现类DefaultSessionAttributeStore实现也简单。(特点:能够制定特殊的前缀,这个有时候还是有用的) // 前缀attributeNamePrefix在构造器里传入进来 默认是“” private final SessionAttributeStore sessionAttributeStore; // 唯一的构造器 handlerType:控制器类型 SessionAttributeStore 是由调用者上层传进来的 public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) { Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null"); this.sessionAttributeStore = sessionAttributeStore; // 父类上、接口上、注解上的注解标注了这个注解都算 SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class); if (ann != null) { Collections.addAll(this.attributeNames, ann.names()); Collections.addAll(this.attributeTypes, ann.types()); } this.knownAttributeNames.addAll(this.attributeNames); } // 既没有指定Name 也没有指定type 这个注解标上了也没啥用 public boolean hasSessionAttributes() { return (!this.attributeNames.isEmpty() || !this.attributeTypes.isEmpty()); } // 看看指定的attributeName或者type是否在包含里面 // 请注意:name和type都是或者的关系,只要有一个符合条件就成 public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) { Assert.notNull(attributeName, "Attribute name must not be null"); if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) { this.knownAttributeNames.add(attributeName); return true; } else { return false; } } // 把attributes属性们存储起来 进到WebRequest 里 public void storeAttributes(WebRequest request, Map<String, ?> attributes) { attributes.forEach((name, value) -> { if (value != null && isHandlerSessionAttribute(name, value.getClass())) { this.sessionAttributeStore.storeAttribute(request, name, value); } }); } // 检索所有的属性们 用的是knownAttributeNames哦~~~~ // 也就是说手动API放进Session的 此处不会被检索出来的 public Map<String, Object> retrieveAttributes(WebRequest request) { Map<String, Object> attributes = new HashMap<>(); for (String name : this.knownAttributeNames) { Object value = this.sessionAttributeStore.retrieveAttribute(request, name); if (value != null) { attributes.put(name, value); } } return attributes; } // 同样的 只会清除knownAttributeNames public void cleanupAttributes(WebRequest request) { for (String attributeName : this.knownAttributeNames) { this.sessionAttributeStore.cleanupAttribute(request, attributeName); } } // 对底层sessionAttributeStore的一个传递调用~~~~~ // 毕竟可以拼比一下sessionAttributeStore的实现~~~~ @Nullable Object retrieveAttribute(WebRequest request, String attributeName) { return this.sessionAttributeStore.retrieveAttribute(request, attributeName); } }
这个类是对SessionAttribute这些属性的核心处理能力:包括了所谓的增删改查。因为要进一步理解到它的原理,所以要说到它的处理入口,那就要来到ModelFactory了~