从原理层面掌握@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来介绍它的基本使用,不出意外阅读下来你对它应该是有很好的收获的,希望能帮助到你简化开发~

相关文章
|
2月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
2月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
19天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
7天前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
2月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
2月前
|
XML JSON 数据库
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
这篇文章详细介绍了RESTful的概念、实现方式,以及如何在SpringMVC中使用HiddenHttpMethodFilter来处理PUT和DELETE请求,并通过具体代码案例分析了RESTful的使用。
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
|
2月前
|
XML Java 数据格式
Spring5入门到实战------2、IOC容器底层原理
这篇文章深入探讨了Spring5框架中的IOC容器,包括IOC的概念、底层原理、以及BeanFactory接口和ApplicationContext接口的介绍。文章通过图解和实例代码,解释了IOC如何通过工厂模式和反射机制实现对象的创建和管理,以及如何降低代码耦合度,提高开发效率。
Spring5入门到实战------2、IOC容器底层原理
|
2月前
|
Java 程序员 数据库连接
女朋友不懂Spring事务原理,今天给她讲清楚了!
该文章讲述了如何解释Spring事务管理的基本原理,特别是针对女朋友在面试中遇到的问题。文章首先通过一个简单的例子引入了传统事务处理的方式,然后详细讨论了Spring事务管理的实现机制。
女朋友不懂Spring事务原理,今天给她讲清楚了!
|
2月前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
|
2月前
|
前端开发 Java Spring
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
48 0
下一篇
无影云桌面