从原理层面掌握@SessionAttributes的使用【享学Spring MVC】(上)

简介: 从原理层面掌握@SessionAttributes的使用【享学Spring MVC】(上)

前言


该注解顾名思义,作用是将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


浏览器如下图:


image.png


初识的小伙伴可以认真的观察本例,它佐证了我上面说的理论知识。


@SessionAttributes注解设置的参数有3类方式去使用它:


  1. 在视图view中(比如jsp页面等)通过request.getAttribute()或session.getAttribute获取
  2. 在后面请求返回的视图view中通过session.getAttribute或者从model中获取(这个也比较常用)
  3. 自动将参数设置到后面请求所对应处理器的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了~

相关文章
|
25天前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
6月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
315 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
6月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
477 0
|
6月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
290 0
|
5月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
2576 114
|
2月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
177 0
|
2月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
111 0
|
2月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
110 0
|
2月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
148 0
|
2月前
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)

热门文章

最新文章