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

相关文章
|
11天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
23 0
|
1月前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
134 9
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
57 2
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
134 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
1月前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
43 0
|
1月前
|
负载均衡 Java API
Spring Cloud原理详解
Spring Cloud原理详解
73 0
|
1月前
|
负载均衡 Java 网络架构
Spring Cloud原理详解
介绍了Spring Cloud的原理和核心组件,包括服务注册与发现、配置管理、负载均衡、断路器、智能路由、分布式消息传递、分布式追踪和服务熔断等,旨在帮助开发人员快速构建和管理微服务架构中的分布式系统。
58 0
下一篇
无影云桌面