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

在这里强烈建议看看我之前写的几篇关于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;
    }   
}

这段程序的运行结果就会是:
Spring MVC 中“拦截器”处理模型数据 (二) @ModelAttribute

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

运行流程:

 * 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 中。

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

Spring MVC 中“拦截器”处理模型数据 (二) @ModelAttribute
如果你的Controller被@SessionAttributes修饰了,而且value也是那个,而且没用@ModelAttribute修饰方法,同时也没有@ModelAttribute修饰目标方法入参。这个时候就会抛出异常。我们知道原理之后很容易去避免这个异常。

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

上一篇:讨喜的隔离可变性(三)创建角色


下一篇:Git 自救指南