天天看点

Spring MVC中@ModelAttribute注解的使用原理

在Spring mvc中,注解@ModelAttribute是一个非常常用的注解,其功能主要在两方面:

  1. 运用在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入Model中,便于View层使用;
  2. 运用在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到Model中;

1)、绑定请求参数到实体对象(表单的命令对象)

@RequestMapping("/register")
public String reigsHer(@ModelAttribute("user") UserDom user) {
    if ("zhangsan".equals(uname) && "123456".equals(upass)) {
        logger.info("成功");
        return "login";
    } else {
        logger.info("失败");
        return "register";
}
           

在上述代码中“@ModelAttribute("user")User user”语句的功能有两个:

  • 将请求参数的输入封装到 user 对象中。
  • 创建 User实例。

以“user”为键值存储在 Model 对象中,和“model.addAttribute("user",user)”语句的功能一样。如果没有指定键值,即“@ModelAttribute UserDom user”,那么在创建 UserDom 实例时以“userDom”为键值存储在 Model 对象中,和“model.addAtttribute("UserDom", user)”语句的功能一样。

注意:以上面的代码为例,这个UserDom类型的实例可能来自哪里呢?有几种可能:

  • 它可能因为@SessionAttributes标注的使用已经存在于model中
  • 它可能因为在同个控制器中使用了@ModelAttribute方法已经存在于model中——正如上一小节所叙述的
  • 它可能是由URI模板变量和类型转换中取得的(下面会详细讲解)
  • 它可能是调用了自身的默认构造器被实例化出来的

@ModelAttribute方法常用于从数据库中取一个属性值,该值可能通过@SessionAttributes标注在请求中间传递。在一些情况下,使用URI模板变量和类型转换的方式来取得一个属性是更方便的方式。这里有个例子:

@RequestMapping(path = "/accounts/{emps}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {

}
           

这个例子中,model属性的名称("account")与URI模板变量的名称相匹配。如果配置了一个可以将String类型的账户值转换成Account类型实例的转换器Converter<String, Account>,那么上面这段代码就可以工作的很好,而不需要再额外写一个@ModelAttribute方法。

下一步就是数据的绑定。WebDataBinder类能将请求参数——包括字符串的查询参数和表单字段等——通过名称匹配到model的属性上。成功匹配的字段在需要的时候会进行一次类型转换(从String类型到目标字段的类型),然后被填充到model对应的属性中。

进行了数据绑定后,则可能会出现一些错误,比如没有提供必须的字段、类型转换过程的错误等。若想检查这些错误,可以在标注了@ModelAttribute的参数紧跟着声明一个BindingResult参数:

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}
           

拿到BindingResult参数后,可以检查是否有错误,可以通过Spring的<errors>表单标签来在同一个表单上显示错误信息。

BindingResult被用于记录数据绑定过程的错误,因此除了数据绑定外,还可以把该对象传给自己定制的验证器来调用验证。这使得数据绑定过程和验证过程出现的错误可以被搜集到一起,然后一并返回给用户:

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    new PetValidator().validate(pet, result);
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}
           

又或者可以通过添加一个JSR-303规范的@Valid标注,这样验证器会自动被调用。

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}
           

2)、 在方法定义中使用@ModelAttribute注解:

除了可以在方法入参上使用@ModelAttribute注解,还可以在方法定义中使用@ModelAttribute注解。SpringMVC在调用目标处理方法前,会先逐个调用在方法级上标注了@ModelAttribute的方法,并将这些方法的返回值添加到模型中。如下:

@ModelAttribute("user")
public User getUser(){          <---1//访问User Controller中的任何一个请求处理方法前,
    User user = new User();         //SpringMVC先执行该方法,并将返回值以user为键添加到模型(model)中 
    user.setUserId("1001");
    return user;
}
@RequestMapping(value="/model")
public String handleModel(@ModelAttribute("user") User user){     <---2 //在此,模型数据(model)会赋给User的入参,
    user.setUserName("tom");                                           //然后再根据HTTP请求消息进一步填充覆盖user对象
    return "/user/show";
}
           

当访问User Controller中的任何一个请求处理方法前,都会事先执行标注了@ModelAttribute的getUser()方法,并将其返回值以user为键添加到隐形模型(Model)中。

由于 ② 处的handleModel()方法使用了入参级的@ModelAttribute注解,且对象属性名和 ① 处方法级@ModelAttribute的对象属性名相同。这时,SpringMVC会将 ① 处获取的模型(Model)属性先赋值给 ② 处的User类型的入参user中,然后再根据HTTP请求消息对user进行填充覆盖,得到一个整合版本的user对象。

提示: 处理方法入参最多只能使用一个SpringMVC的注解,如 handleModel (@ModelAttribute("user") User user)的user入参使用了@ModelAttribute,就不能再使用@RequestParam或@CookieValue。如果使用了两个注解,SpringMVC就跑出异常。 

注意:

①如果目标方法的POJO类型的参数没有使用@ModelAttribute作为入参修饰,则key为POJO类名的第一个字母小写

例test(User user){}这样的方法key就是user

②如果使用了@ModelAttribute来修饰入参,则key为@ModelAttribute注解的value属性值

例test(@ModelAttribute(value="users") User user){} 这样的方法key就是users

在Model中查找key对应的对象,若果存在,则作为入参传入

如果在@ModelAttribute标记的方法在Map中保存过这个key的对象,则会获取到

若果不存在,则检查这个类是否被@SessionAttributes注解修饰,如果使用该注解,这个注解中value包含了key,

  1. 则会从HttpSession中获取key对应的对象,如果存在,则直接传入目标方法的入参中,若不存在,则抛出异常
  2. 如果这个类没有被@SessionAttributes注解,或者注解的value中不包含key,则会通过反射创建POJO类型的参数,传入为目标方法的参数
  3. SpringMVC会把key和POJO类型的对象保存到Model中,进入保存到request中

注:由于参考网上内容太多,忘记了出处...如有侵权请告知,Email: [email protected]