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

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

ModelFactory


Spring MVC对@SessionAttributes的处理操作入口,是在ModelFactory.initModel()方法里会对@SessionAttributes的注解进行解析、处理,然后方法完成之后也会对它进行属性同步。


ModelFactory是用来维护Model的,具体包含两个功能:


  • 处理器执行前,初始化Model
  • 处理器执行后,将Model中相应的参数同步更新到SessionAttributes中(不是全量,而是符合条件的那些)


// @since 3.1
public final class ModelFactory {
  // ModelMethod它是一个私有内部类,持有InvocableHandlerMethod的引用  和方法的dependencies依赖们
  private final List<ModelMethod> modelMethods = new ArrayList<>();
  private final WebDataBinderFactory dataBinderFactory;
  private final SessionAttributesHandler sessionAttributesHandler;
  public ModelFactory(@Nullable List<InvocableHandlerMethod> handlerMethods, WebDataBinderFactory binderFactory, SessionAttributesHandler attributeHandler) {
    // 把InvocableHandlerMethod转为内部类ModelMethod
    if (handlerMethods != null) {
      for (InvocableHandlerMethod handlerMethod : handlerMethods) {
        this.modelMethods.add(new ModelMethod(handlerMethod));
      }
    }
    this.dataBinderFactory = binderFactory;
    this.sessionAttributesHandler = attributeHandler;
  }
  // 该方法完成Model的初始化
  public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception {
    // 先拿到sessionAttr里所有的属性们(首次进来肯定木有,但同一个session第二次进来就有了)
    Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    // 和当前请求中 已经有的model合并属性信息
    // 注意:sessionAttributes中只有当前model不存在的属性,它才会放进去
    container.mergeAttributes(sessionAttributes);
    // 此方法重要:调用模型属性方法来填充模型  这里ModelAttribute会生效
    // 关于@ModelAttribute的内容  我放到了这里:https://blog.csdn.net/f641385712/article/details/98260361
    // 总之:完成这步之后 Model就有值了~~~~
    invokeModelAttributeMethods(request, container);
    // 最后,最后,最后还做了这么一步操作~~~
    // findSessionAttributeArguments的作用:把@ModelAttribute的入参也列入SessionAttributes(非常重要) 详细见下文
    // 这里一定要掌握:因为使用中的坑坑经常是因为没有理解到这块逻辑
    for (String name : findSessionAttributeArguments(handlerMethod)) {
      // 若ModelAndViewContainer不包含此name的属性   才会进来继续处理  这一点也要注意
      if (!container.containsAttribute(name)) {
        // 去请求域里检索为name的属性,若请求域里没有(也就是sessionAttr里没有),此处会抛出异常的~~~~
        Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
        if (value == null) {
          throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
        }
        // 把从sessionAttr里检索到的属性也向容器Model内放置一份~
        container.addAttribute(name, value);
      }
    }
  }
  // 把@ModelAttribute标注的入参也列入SessionAttributes 放进sesson里(非常重要)
  // 这个动作是很多开发者都忽略了的
  private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
    List<String> result = new ArrayList<>();
    // 遍历所有的方法参数
    for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
      // 只有参数里标注了@ModelAttribute的才会进入继续解析~~~
      if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
        // 关于getNameForParameter拿到modelKey的方法,这个策略是需要知晓的
        String name = getNameForParameter(parameter);
        Class<?> paramType = parameter.getParameterType();
        // 判断isHandlerSessionAttribute为true的  才会把此name合法的添加进来
        // (也就是符合@SessionAttribute标注的key或者type的)
        if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) {
          result.add(name);
        }
      }
    }
    return result;
  }
  // 静态方法:决定了parameter的名字  它是public的,因为ModelAttributeMethodProcessor里也有使用
  // 请注意:这里不是MethodParameter.getParameterName()获取到的形参名字,而是有自己的一套规则的
  // @ModelAttribute指定了value值就以它为准,否则就是类名的首字母小写(当然不同类型不一样,下面有给范例)
  public static String getNameForParameter(MethodParameter parameter) {
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    String name = (ann != null ? ann.value() : null);
    return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter));
  }
  // 关于方法这块的处理逻辑,和上差不多,主要是返回类型和实际类型的区分
  // 比如List<String>它对应的名是:stringList。即使你的返回类型是Object~~~
  public static String getNameForReturnValue(@Nullable Object returnValue, MethodParameter returnType) {
    ModelAttribute ann = returnType.getMethodAnnotation(ModelAttribute.class);
    if (ann != null && StringUtils.hasText(ann.value())) {
      return ann.value();
    } else {
      Method method = returnType.getMethod();
      Assert.state(method != null, "No handler method");
      Class<?> containingClass = returnType.getContainingClass();
      Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
      return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
    }
  }
  // 将列为@SessionAttributes的模型数据,提升到sessionAttr里
  public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
    ModelMap defaultModel = container.getDefaultModel();
    if (container.getSessionStatus().isComplete()){
      this.sessionAttributesHandler.cleanupAttributes(request);
    } else { // 存储到sessionAttr里
      this.sessionAttributesHandler.storeAttributes(request, defaultModel);
    }
    // 若该request还没有被处理  并且 Model就是默认defaultModel
    if (!container.isRequestHandled() && container.getModel() == defaultModel) {
      updateBindingResult(request, defaultModel);
    }
  }
  // 将bindingResult属性添加到需要该属性的模型中。
  // isBindingCandidate:给定属性在Model模型中是否需要bindingResult。
  private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
    List<String> keyNames = new ArrayList<>(model.keySet());
    for (String name : keyNames) {
      Object value = model.get(name);
      if (value != null && isBindingCandidate(name, value)) {
        String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
        if (!model.containsAttribute(bindingResultKey)) {
          WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request, value, name);
          model.put(bindingResultKey, dataBinder.getBindingResult());
        }
      }
    }
  }
  // 看看这个静态内部类ModelMethod
  private static class ModelMethod {
    // 持有可调用的InvocableHandlerMethod 这个方法
    private final InvocableHandlerMethod handlerMethod;
    // 这字段是搜集该方法标注了@ModelAttribute注解的入参们
    private final Set<String> dependencies = new HashSet<>();
    public ModelMethod(InvocableHandlerMethod handlerMethod) {
      this.handlerMethod = handlerMethod;
      // 把方法入参中所有标注了@ModelAttribute了的Name都搜集进来
      for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
        if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
          this.dependencies.add(getNameForParameter(parameter));
        }
      }
    }
    ...
  }
}


ModelFactory协助在控制器方法调用之前初始化Model模型,并在调用之后对其进行更新。


  • 初始化时,通过调用方法上标注有@ModelAttribute的方法,使用临时存储在会话中的属性填充模型。
  • 在更新时,模型属性与会话同步,如果缺少,还将添加BindingResult属性。


关于默认名称规则的核心在Conventions.getVariableNameForParameter(parameter)这个方法里,我在上文给了一个范例,介绍常见的各个类型的输出值,大家记忆一下便可。参考:从原理层面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【享学Spring MVC】


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


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

在处理器(Controller)中将参数设置到了Model中(这样方法结束后会自动的同步到SessionAttr里)


总结


@SessionAttributes指的是Spring MVC的Session。向其中添加值得时候,同时会向 HttpSession中添加一条。在sessionStatus.setComplete();的时候,会清空Spring MVC

的Session,同时清除对应键的HttpSession内容,但是通过,request.getSession.setAttribute()方式添加的内容不会被清除掉。其他情况下,Spring MVC的Session和HttpSession使用情况相同。


这篇文章介绍了@SessionAttributes的核心处理原理,以及也给了一个Demo来介绍它的基本使用,不出意外阅读下来你对它应该是有很好的收获的,希望能帮助到你简化开发~

相关文章
|
5月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
686 22
|
5月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
1964 0
|
4月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
4月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
4月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
565 2
|
6月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
7月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
524 0
|
7月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
260 0
|
7月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
218 0
|
7月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
325 0

热门文章

最新文章