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

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

再看下面的变种例子(重要):


@RestController
@RequestMapping
@SessionAttributes(names = {"name", "age"}, types = Person.class)
public class HelloController {
    @GetMapping("/testModelAttr")
    public void testModelAttr(@ModelAttribute Person person, HttpSession httpSession, ModelMap modelMap) {
        System.out.println(modelMap.get("person"));
        System.out.println(httpSession.getAttribute("person"));
    }
}


访问:/testModelAttr?name=wo&age=10。报错了:


 org.springframework.web.HttpSessionRequiredException: Expected session attribute 'person'
  at org.springframework.web.method.annotation.ModelFactory.initModel(ModelFactory.java:117)
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:869)


这个错误请务必重视:这是前面我特别强调的一个使用误区,当你在@SessionAttributes@ModelAttribute一起使用的时候,最容易犯的一个错误。


错误原因代码如下:


  public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception {
    Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    container.mergeAttributes(sessionAttributes);
    invokeModelAttributeMethods(request, container);
    // 合并完sesson的属性,并且执行完成@ModelAttribute的方法后,会继续去检测
    // findSessionAttributeArguments:标注有@ModelAttribute的入参  并且isHandlerSessionAttribute()是SessionAttributts能够处理的类型的话
    // 那就必须给与赋值~~~~  注意是必须
    for (String name : findSessionAttributeArguments(handlerMethod)) {
      // 如果model里不存在这个属性(那就去sessionAttr里面找)
      // 这就是所谓的其实@ModelAttribute它是会深入到session里面去找的哦~~~不仅仅是request里
      if (!container.containsAttribute(name)) {
        Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
        // 倘若session里都没有找到,那就报错喽
        // 注意:它并不会自己创建出一个新对象出来,然后自己填值,这就是区别。
        // 至于Spring为什么这么设计 我觉得是值得思考一下子的
        if (value == null) {
          throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
        }
        container.addAttribute(name, value);
      }
    }
  }


注意,这里是initModel()的时候就报错了哟,还没到resolveArgument()呢。Spring这样设计的意图???我大胆猜测一下:控制器上标注了@SessionAttributes注解,如果你入参上还使用了@ModelAttribute,那么你肯定是希望得到绑定的,若找不到肯定是你的程序失误有问题,所以给你抛出异常,显示的告诉你要去排错。


修改如下,本控制器上加上这个方法:

    @ModelAttribute
    public Person personModelAttr() {
        return new Person("非功能方法", 50);
    }

(请注意观察下面的几次访问以及对应的打印结果)

访问:/testModelAttr


Person(name=非功能方法, age=50)
null


再访问:/testModelAttr

Person(name=非功能方法, age=50)
Person(name=非功能方法, age=50)


访问:/testModelAttr?name=wo&age=10

Person(name=wo, age=10)
Person(name=wo, age=10)


注意:此时model和session里面的值都变了哦,变成了最新的的请求链接上的参数值(并且每次都会使用请求参数的值)。


访问:/testModelAttr?age=11111

Person(name=wo, age=11111)
Person(name=wo, age=11111)

可以看到是可以完成局部属性修改的

再次访问:/testModelAttr(无请求参数,相当于只执行非功能方法)

Person(name=fsx, age=18)
Person(name=fsx, age=18)

可以看到这个时候model和session里的值已经不能再被非功能方法上的@ModelAttribute所改变了,这是一个重要的结论。

它的根本原理在这里:


  public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception {
    ...
    invokeModelAttributeMethods(request, container);
    ...
  }
  private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
    while (!this.modelMethods.isEmpty()) {
      ...
      // 若model里已经存在此key 直接continue了
      if (container.containsAttribute(ann.name())) {
        ...
        continue;
      }
      // 执行方法
      Object returnValue = modelMethod.invokeForRequest(request, container);
      // 注意:这里只判断了不为void,因此即使你的returnValue=null也是会进来的
      if (!modelMethod.isVoid()){
        ...
        // 也是只有属性不存在 才会生效哦~~~~
        if (!container.containsAttribute(returnValueName)) {
          container.addAttribute(returnValueName, returnValue);
        }
      }
    }
  }


因此最终对于@ModelAttribute和@SessionAttributes共同的使用的时候务必要注意的结论:已经添加进session的数据,在没用使用SessionStatus清除过之前,@ModelAttribute标注的非功能方法的返回值并不会被再次更新进session内


所以@ModelAttribute标注的非功能方法有点初始值的意思哈~,当然你可以手动SessionStatus清楚后它又会生效了


总结


任何技术最终都会落到使用上,本文主要是介绍了@ModelAttribute各种使用case的示例,同时也指出了它和@SessionAttributes一起使用的坑。

@ModelAttribute这个注解相对来说还是使用较为频繁,并且功能强大,也是最近讲的最为重要的一个注解,因此花的篇幅较多,希望对小伙伴们的实际工作中带来帮助,带来代码之美~

相关文章
|
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
下一篇
无影云桌面