Spring MVC 处理模型数据

对于 MVC 框架来说,模型数据是最重要的,因为控制(C)是为了产生模型数据(M),而视图(V)则是为了渲染模型数据。

通过前面的学习我们己经知道,Spring MVC 通过 @RequestMapping 将请求引导到处理方法上,使用合适的方法签名将请求消息绑定到入参中。方法入参绑定请求消息只是处理方法的第一步,还有更为重要的任务等待完成,即根据入参执行相应的逻辑,产生模型数据,导向到特定视图中。

将模型数据暴露给视图是 Spring MVC 框架的一项重要工作。Spring MVC 提供了多种途径输出模型数据,介绍如下。
1)ModelAndView:当处理方法返回值类型为 ModelAndView 时,方法体即可通过该对象添加模型数据。

2)@ModelAttribute:在方法入参标注该注解后,入参的对象就会放到数据模型中。

3)Map及Model:如果方法入参为org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.util.Map,则当处理方法返回时,Map 中的数据会自动添加到模型中。

4)@SessionAttributes:将模型中的某个属性暂存到 HttpSession 中,以便多个请求之间可以共享这个属性。

1.ModelAndView

控制器处理方法的返回值如果为 ModelAndView,则其既包含视图信息,又包含模型数据信息,这样 Spring MVC 就可以使用视图对模型数据进行渲染了。可以简单地将模型数据看成一个Map<String,Object>对象。

在处理方法的方法体中,可以使用如下方法添加模型数据。

1)ModelAndView addObject(String attributeName,Object attributeValue)。

2)ModelAndView addAIIObjects(Map<String,?>  modelMap)。

可以通过如下方法设置视图。

1)void setView(View view):指定一个具体的视图对象。

2)void setView(String viewName):指定一个逻辑视图名。

2.@ModelAttribute

如果希望将方法入参对象添加到模型中,则仅需在相应入参前使用 @ModelAttribute 注解即可。来看一个具体的实例,如下面代码所示。

@RequestMapping(value = "/handle61")
public String handle61(@ModelAttribute("user") User user) {
  user.setUserId("1000");
  return "/user/createSuccess";
}

Spring MVC 将请求消息绑定到 User 对象中,然后再以 user 为键将 User 对象放到模型中。在准备对视图进行渲染前,Spring MVC还会进一步将模型中的数据转储到视图的上下文中并暴露给视图对象。对于JSP视图来说,Spring MVC 会将模型数据转储到 ServletRequest 的属性列表中(通过 ServletRequest#setAttribute(String name,Object o) 方法保存)。

handle61() 方法返回的逻辑视图名为 /user/createSuccess,对应 createSuccess.jsp 视图对象,这样 createSuccess.jsp 就可以使用  ${user.userName} 等方式顺利地访问到模型中的数据了。

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

@ModelAttribute("user")
public User getUser() {      //①
    User user = new User();
    user.setUserId("1001");
    return user;
}

@RequestMapping(value = "/handle62")
public String handle62(@ModelAttribute("user") User user) {    //②
    user.setUserName("tom");
    return "/user/showUser";
}

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

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

处理方法入参最多只能使用一个 Spring MVC 的注解,如 handle62(@ModelAttribute("user") User user) 的 user 入参使用了 @ModelAttribute,就不能再使用 @RequestParam 或 @CookieValue。如果使用了两个注解,则 Spring MVC 将抛出异常。

3.Map及Model

Spring MVC 在内部使用一个 org.springframework.ui.Model 接口存储模型数据,它的功能类似于 java.util.Map,但它比 Map 易用。org.springframework.ui.ModelMap 实现了 Map 接口,而 org.springframework.ui.ExtendedModelMap 扩展于 ModelMap 的同时实现了 Model 接口。

Spring MVC 在调用方法前会创建一个隐含的模型对象,作为模型数据的存储容器,我们称之为“隐含模型”。如果处理方法的入参为 Map 或 Model 类型,则 Spring MVC 会将隐含模型的引用传递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据。来看一个简单的例子,如下面代码所示。

@ModelAttribute("user")
public User getUser() {
    User user = new User();
    user.setUserId("1001");
    return user;
}

@RequestMapping(value = "/handle63")
public String handle63(ModelMap modelMap) {
    User user = (User) modelMap.get("user");
    user.setUserName("tom");
    modelMap.addAttribute("testAttr", "value1");
    return "/user/showUser";
}

Spring MVC 一旦发现处理方法有 Map 或 Model 类型的入参,就会将请求内在的隐含模型对象传递给这些参数,因此在方法体中可以通过这个入参对模型中的数据进行读/写操作。

4. @SessionAttributes

如果希望在多个请求之间共用某个模型属性数据,则可以在控制器类中标注一个 @SessionAttributes,Spring MVC 会将模型中对应的属性暂存到 HttpSession 中。来看一个实例。

@Controller
@RequestMapping("/user")
@SessionAttributes("user")//① 将②处的模型属性自动保存到HttpSession中
public class UserController {
    
    @RequestMapping(value = "/handle71")
    public String handle71(@ModelAttribute("user") User user) {//
        user.setUserName("John");
        return "redirect:handle72.html";
    }

    @RequestMapping(value = "/handle72")
    public String handle72(ModelMap modelMap, SessionStatus sessionStatus) {
        User user = (User) modelMap.get("user");//③读取模型中的数据
        if (user != null) {
            user.setUserName("Jetty");
            sessionStatus.setComplete();//④让Spring MVC清除本处理器对应的会话属性
        }
        return "/user/showUser";
    }
}

在①处标注的 @SessionAttributes("user") 会自动将本处理器中任何处理方法属性名为 user 的模型属性透明地存储到 HttpSession中。在②处,handle71() 方法的 User user 入参会添加到隐含模型中,于是这个模型属性在 handle71() 方法执行时,会由 Spring MVC 将其透明地保存到 HttpSession 中。

handle71() 返回的逻辑视图名为 redirect:handle72.html,它将发起另一个请求,而这个请求由 handle72() 负责处理。handle72() 和 handle71() 位于不同的请求上下文中,之所以在③处可以获取名为 user 的模型属性,就是因为 @SessionAttributes("user") 透明地将 handle71() 的 user 模型属性存储到 HttpSession中,而 handle72() 的隐含模型又自动从 HttpSession 中获取到这个模型属性。

我们知道,在一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理。但如果字符串带 "forward:” 或 “redirect:” 前缀,则 Spring MVC 将对它们进行特殊处理:将 “forward:” 或 "redirect:” 当成指示符,其后的字符作为 URL 处理,如 "redirect:handle72.html” 或 " forward:http://www.baidu.com"。redirect 会让浏览器发起一个新的请求,而 forward 请求与当前请求同属一个请求。

handle72() 方法还包含一个 SessionStatus 入参,当调用 SessionStatus#setComplete() 方法时,Spring MVC会清除该控制器类的所有会话属性;否则这个会话属性会一直保存在 HttpSession 中。

很可惜,当启动 web 应用并向 handle71() 发送请求时,Spring MVC 会抛出以下异常信息:

分析代码,由于在①处标注了 @SessionAttributes("user"),因此 user 为会话属性,Spring MVC 在对handle71(@ModelAttribute("user")Useruser)进行处理时,会先在隐含模型中查询是否有对应的属性,如果不存在,则继续在会话中查询该属性。由于在会话中也不存在该属性,因此报 HttpSessionRequiredException 异常。

解决该异常的方法很简单,只需添加一个标注 @ModelAttribute("user") 的方法,以便让 Spring MVC 在处理handle71(@ModelAttribute("user") User user) 方法前先向隐含模型中添加 user 属性,这样4.1就会执行,而4.2步不会执行,这样就不会抛出 HttpSessionRequiredException 异常,如下面代码所示。

@Controller
@RequestMapping("/user")
@SessionAttributes("user")
public class UserController {
    
    @ModelAttribute("user")
    public User getUser() {  //①该方法会向隐含模型中添加一个名为 user 的模型属性
        User user = new User();
        user.setUserId("1001");
        return user;
    }
    
    @RequestMapping(value = "/handle71")
    public String handle71(@ModelAttribute("user") User user) {
        user.setUserName("John");
        return "redirect:handle72.html";
    }

    @RequestMapping(value = "/handle72")
    public String handle72(ModelMap modelMap, SessionStatus sessionStatus) {
        User user = (User) modelMap.get("user");
        if (user != null) {
            user.setUserName("Jetty");
            sessionStatus.setComplete();
        }
        return "/user/showUser";
    }
}

@SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。如 @SessionAttributes(types=User.class) 会将隐含模型中所有类型为 User.class 的属性添加到会话中。

此外,@SessionAttributes 还可以通过属性名及 types 一起指定,二者都允许多值,放到会话中的属性是二者的并集。如以下声明方式都是合法的。

1)@SessionAttributes(value={"user1","user2"}):将名为 user1 及 user2 的模型属性添加到会话中。

2)@SessionAttributes(types={User.class, Dept.class}):将模型中所有类型为 User 及 Dept 的属性添加到会话中。

3)@SessionAttributes(value={"user1","user2"}, types={Dept.class}):将名为 user1 及 user2 的模型属性添加到会话中,同时将所有类型为 Dept 的模型属性添加到会话中。

原文地址:https://www.cnblogs.com/jwen1994/p/11147013.html