Spring MVC 中“拦截器”处理模型数据 (二) @ModelAttribute

    xiaoxiao2025-08-23  12

    在这里强烈建议看看我之前写的几篇关于SpringMVC的博客,都是串通的。

    @ModelAttribute这个是SpringMVC中处理模型数据的最难也是最重要的点。相当于以前Struct的拦截器。

    用途:比如我们要修改一个对象的部分数据,按照以前的思维,new一个对象保存数据,然后赋值,把不修改数据先拿出来保存起来。但是这个已经Out了, 在SpringMVC中,是拿到数据库的实例,然后把传进来的值也就是需要修改的值set进去,那么没有set的值即为不需要修改的值。

    index.jsp

    <!-- 模拟修改操作 1. 原始数据 : 1, yexx, 123456, yexx@hust.com, 12 2. 密码不能被修改 3. 表单回显, 模拟操作直接在表单填写对应的属性值 --> <form action="springmvc/testModelAttributes" method="post"> <input type="hidden" name="id" value="1"/> username: <input type="text" name="username" value="yexx"/> <br/> email: <input type="text" name="email" value="yexx@hust.com"/> <br/> age: <input type="text" name="age" value="13"/> <br/> <input type="submit" value="Submit"/> </form> package com.hust.springmvc1; import java.util.Map; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import com.hust.springmvc.entities.User; //@SessionAttributes(value={"user"}, types={String.class}) @Controller @RequestMapping("/springmvc") public class SpringMVCTest { private static final String SUCCESS = "success"; /** * 1. 由@ModelAttribute 标记的方法,会在每个目标方法执行之前被SpringMVC调用! * 2. @ModelAttribute 注解也可以来修饰目标方法POJO 类型的入参,且value 属性值如下的作用: * 1). SpringMVC 会使用value 属性值 在implicitModel 中查找对应的对象,若存在则会直接传入到目标方法的入参中。 * 2). SpringMVC 会一value 为 key , POJO 类型的对象为value, 存入request 中。 */ @ModelAttribute public void getUser(@RequestParam(value="id", required=false) Integer id, Map<String, Object> map) { System.out.println("ModelAttribute"); if (id!=null) { //模拟从数据库中获取对象 User user = new User(1, "yexx", "123456", "yexx@hust.com", 12); System.out.println("从数据库中获取一个对象:" + user); map.put("user", user); } } /** * 运行流程: * 1. 执行@ModelAttribute 注解修饰的方法:从数据库中取出对象,把对象放入到Map中。键为:user * 2. SpringMVC 从 Map 中取出user对象, 并把表单的请求参数赋给该User 对象对应的属性。 * 3. SpringMVC 把上述对象传入目标方法的参数。 * * 注意: 在@ModelAttribute 修饰的方法中,放入到Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致 * * SpringMVC 确定目标方法POJO 类型入参的过程 * 1. 确定一个key: * 1). 若目标方法的POJO类型的参数木有使用@ModelAttribute 作为修饰,则key 为 POJO类名第一个字母的小写 * 2). 若使用了@ModelAttribute 来修饰, 则key 为@ModelAttribute 注解的value属性值。 * 2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入 * 1). 若在@ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的key 一致,则会获取到。 * 3. 若 implicitModel 中不存在key 对应的对象, 则检查当前的 Handler 是否使用了@SessionAttributes 注解修饰, * 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了key, 则会从HttpSession 中来获取 key 所对应 * 的value 的值, 若存在则直接传入到目标方法的入参中, 若不存在则将抛出异常。 * 4. 若Handler 没有表示@SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则会 * 通过反射来创建 POJO类型的参数, 传入为目标方法的参数 * 5. SpringMVC 会把key 和 POJO 类型的对象 保存到implicitModel 中, 进而会保存到 request 中。 * * 源代码分析的流程 * 1. 调用@ModelAttribute 注解修饰的方法。 实际上把@ModelAttribute 方法中Map 中的数据放在了 implicitModel中。 * 2. 解析请求处理器的目标参数,实际上该目标参数来自于 WebDataBinder 对象的 target属性 * 1). 创建WebDataBinder 对象 * ① 确定objectName 属性: 若传入的attrName 属性值为空,则objectName 为类名第一个字母小写。 * *注意: attrName 若目标方法的POJO属性使用了@ModelAttribute 来修饰,则attrName 值即为 @ModelAttribute的value值 * ② 确定target 属性: * > 在implicitModel 中查找 attrName 对应的属性值。 若存在。 OK * > 若不存在: 则验证当前Handler 是否使用了@sessionAttributes 进行修饰,若使用了,则尝试从Session中获取attrName 所对应 * 的属性值。 若session 中没有对应的属性值,则抛出异常 * > 若Handler 没有使用@SessionAttributes 进行修饰, 或@SessionAttribute 中没有使用 value 值制定的 key * 和 attrName 相匹配,则通过反射创建POJO对象 * * 2). SpringMVC 把表单的请求参数赋给了WebDataBinder 的target 属性 * 3). *SpringMVC 会把WebDataBinder 的attrName 和 target 给到 implicitModel * 4). 把WebDataBinder 的 target 作为参数传递给目标方法入参。 */ @RequestMapping("/testModelAttributes") public String testModelAttributes(@ModelAttribute("user") User user) { System.out.println("update:" + user); return SUCCESS; } }

    这段程序的运行结果就会是:

    该回显数据也是能够显示出来。我把执行过程和源码分析拿出来特别的说一下。

    运行流程:

    * 1. 执行@ModelAttribute 注解修饰的方法:从数据库中取出对象,把对象放入到Map中。键为:user * 2. SpringMVC 从 Map 中取出user对象, 并把表单的请求参数赋给该User 对象对应的属性。 * 3. SpringMVC 把上述对象传入目标方法的参数。 * * 注意: 在@ModelAttribute 修饰的方法中,放入到Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致。

    源代码分析的流程

    * 1. 调用@ModelAttribute 注解修饰的方法。 实际上把@ModelAttribute 方法中Map 中的数据放在了 implicitModel中。 * 2. 解析请求处理器的目标参数,实际上该目标参数来自于 WebDataBinder 对象的 target属性 * 1). 创建WebDataBinder 对象 * ① 确定objectName 属性: 若传入的attrName 属性值为空,则objectName 为类名第一个字母小写。 * *注意: attrName 若目标方法的POJO属性使用了@ModelAttribute 来修饰,则attrName 值即为 @ModelAttribute的value值 * ② 确定target 属性: * > 在implicitModel 中查找 attrName 对应的属性值。 若存在。 OK * > 若不存在: 则验证当前Handler 是否使用了@sessionAttributes 进行修饰,若使用了,则尝试从Session中获取attrName 所对应 * 的属性值。 若session 中没有对应的属性值,则抛出异常 * > 若Handler 没有使用@SessionAttributes 进行修饰, 或@SessionAttribute 中没有使用 value 值制定的 key * 和 attrName 相匹配,则通过反射创建POJO对象 * * 2). SpringMVC 把表单的请求参数赋给了WebDataBinder 的target 属性 * 3). *SpringMVC 会把WebDataBinder 的attrName 和 target 给到 implicitModel * 4). 把WebDataBinder 的 target 作为参数传递给目标方法入参。

    SpringMVC 确定目标方法POJO 类型入参的过程

    * 1. 确定一个key: * 1). 若目标方法的POJO类型的参数木有使用@ModelAttribute 作为修饰,则key 为 POJO类名第一个字母的小写 * 2). 若使用了@ModelAttribute 来修饰, 则key 为@ModelAttribute 注解的value属性值。 * 2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入 * 1). 若在@ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的key 一致,则会获取到。 * 3. 若 implicitModel 中不存在key 对应的对象, 则检查当前的 Handler 是否使用了@SessionAttributes 注解修饰, * 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了key, 则会从HttpSession 中来获取 key 所对应 * 的value 的值, 若存在则直接传入到目标方法的入参中, 若不存在则将抛出异常。 * 4. 若Handler 没有表示@SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则会 * 通过反射来创建 POJO类型的参数, 传入为目标方法的参数 * 5. SpringMVC 会把key 和 POJO 类型的对象 保存到implicitModel 中, 进而会保存到 request 中。

    总结

    * 1. 由@ModelAttribute 标记的方法,会在每个目标方法执行之前被SpringMVC调用! * 2. @ModelAttribute 注解也可以来修饰目标方法POJO 类型的入参,且value 属性值如下的作用: * 1). SpringMVC 会使用value 属性值 在implicitModel 中查找对应的对象,若存在则会直接传入到目标方法的入参中。 * 2). SpringMVC 会一value 为 key , POJO 类型的对象为value, 存入request 中。

    这里有一个特别值得注意的地方

    如果你的Controller被@SessionAttributes修饰了,而且value也是那个,而且没用@ModelAttribute修饰方法,同时也没有@ModelAttribute修饰目标方法入参。这个时候就会抛出异常。我们知道原理之后很容易去避免这个异常。

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)