zoukankan      html  css  js  c++  java
  • Spring MVC 框架学习三:Spring MVC 处理模型数据

     

    SpringMVC 中模型数据的产生

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

    如何将模型数据暴露给视图是 SpringMVC 框架的一项重要工作,SpringMVC 提供了多种途径输出模型数据:

    1. ModelAndView:处理方法返回值类型为 ModelAndView时,方法体即可通过该对象添加模型数据。

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

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

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

     

    ModelAndView

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

    在处理方法的方法体中,可以使用如下方法添加模型对象:

    1. ModelAndView addObject(String attributeName, Object attributeValue)

    2. ModelAndView addAllObject(Map<String, ?> modelMap)

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

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

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

    配置web.xml

        <servlet>
            <servlet-name>springDispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>springDispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>

    配置 springDispatcherServlet-servlet.xml 

        <!-- 配置自动扫描的包 -->
        <context:component-scan base-package="com.bupt.springmvc.handler"/>
    
        <!-- 配置视图解析器:将视图逻辑名解析为/WEB-INF/viewss/<viewsName>.jsp (需在/WEB-INF下新建一个名为views的文件夹) -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/views/"/>
            <property name="suffix" value=".jsp"/>
        </bean>

    新建测试类 SpringTest

     1 package com.bupt.springmvc.handler;
     2 
     3 import org.springframework.stereotype.Controller;
     4 import org.springframework.web.servlet.ModelAndView;
     5 
     6 @RequestMapping("/springmvc")
     7 @Controller
     8 public class SpringTest
     9 {    
    10     //SpringMVC 会把 ModelAndView 的模型数据放到请求域中去
    11     @RequestMapping("/testModelAndView")
    12     public ModelAndView testModelAndView()
    13     {
    14         String viewName = "success";
    15      //指定跳转的逻辑视图名为 success
    16         ModelAndView modelAndView = new ModelAndView(viewName);
    17         //添加模型数据到ModelAndView
    18         modelAndView.addObject("time", new Date());
    19         return modelAndView;
    20     }
    21 }

    新建请求页面 index.jsp

    <head>
    <title>Insert title here</title>
    </head>
    <body>
        <a href="springmvc/testModelAndView">Test ModelAndView</a>
    </body>
    </html>

    结果页面 success.jsp

    <html>
    <head>
    <title>Insert title here</title>
    </head>
    <body>
        <h4>Success Page</h4>
        
        time: ${requestScope.time }
    </body>
    </html>

    Map 及 Model

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

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

    SpringTest类中新增方法

    1     //目标方法可以添加 Map 类型的参数,也可以是 Model 类型或者 ModelMap 类型的参数
    2     @RequestMapping("/testMap")
    3     public String testMap(Map<String, Object> map)
    4     {
    5         map.put("names", Arrays.asList("tom", "jack", "mike"));
    6         return "success";
    7     }

    index.jsp新增超链接

    <a href="springmvc/testMap">Test Map</a>

    success.jsp新增如下代码

    map: ${requestScope.names }

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

    @SessionAttributes

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

    @SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。

    1. @SessionAttributes(types=User.class)会将隐含模型中所有类型为 User 的属性添加到会话中

    2. @SessionAttributes(value={"user1", "user2"})将名为 user1 和 user2 的模型属性添加到会话中

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

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

    新建一个 POJO

    package com.bupt.springmvc.entity;
    
    public class User
    {
        private int id;
        private String username;
        private String password;
        private String address;
    
      //生成 get 和 setter 方法,生成带参和不带参的构造方法,重写 toString 方法
    }
     1 @SessionAttributes(value={"user"}, types={String.class})
     2 @RequestMapping("/springmvc")
     3 @Controller
     4 public class SpringTest
     5 {
     6     private static final String SUCCESS = "success";
     7     
     8   @RequestMapping("/testSessionAttributes")
     9     public String testSessionAttributes(Map<String, Object> map)
    10     {
    11         User user = new User("Jack", "123");
    12         map.put("user", user);
    13         map.put("msg", "hello");
    14         return SUCCESS;
    15     }
    16 }
    <a href="springmvc/testSessionAttributes">Test SessionAttributes</a>
    1 request user: ${requestScope.user }
    2 <br><br>
    3 request msg: ${requestScope.msg }
    4 <br><br>
    5 session user: ${sessionScope.user }
    6 <br><br>
    7 session msg: ${sessionScope.msg }

    由结果可以看出,被 @SessionAttributes 注解修饰后,模型属性不仅存在于请求域还存在于会话域。除了可以通过value属性值指定需要放到会话中的属性外,还可以根据types属性值指定哪些类型的模型属性需要放到会话中。需要注意的是 @SessionAttributes 注解只能用于修饰类而不能用于方法上

     

    @ModelAttribute

    在方法定义上使用 @ModelAttribute 注解:SpringMVC 在调用目标处理方法前,会逐个调用在方法上标注了 @ModelAttribute 的方法

    在方法的入参前使用 @ModelAttribute 注解

    1. 可以从隐含对象中获取隐含的模型数据对象,再将请求参数绑定到对象中,再传入入参

    2. 将方法入参对象添加到模型中

    通过例子来说明

     1 @Controller  
     2 public class HelloModelController {  
     3       
     4     @ModelAttribute   
     5     public void populateModel(@RequestParam String abc, Model model) {    
     6        model.addAttribute("attributeName", abc);    
     7     }    
     8   
     9     @RequestMapping(value = "/helloWorld")    
    10     public String helloWorld() {    
    11        return "helloWorld";    
    12     }    
    13 }  

    在这个代码中,访问控制器方法helloWorld时,会首先调用populateModel方法,将页面参数abc放到model的attributeName属性中,在视图中可以直接访问

     1 @Controller  
     2 public class Hello2ModelController {  
     3       
     4     @ModelAttribute   
     5     public User populateModel() {    
     6        User user=new User();  
     7        user.setAccount("ray");  
     8        return user;  
     9     }    
    10     @RequestMapping(value = "/helloWorld2")    
    11     public String helloWorld() {    
    12        return "helloWorld";    
    13     }    
    14 }  

    当用户请求/helloWorld2时,首先访问populateModel方法,返回User对象,model属性的名称没有指定,它由返回类型隐含表示,如这个方法返回User类型,那么这个model属性的名称是user。这个例子中model属性名称由返回对象类型隐含表示,model属性对象就是方法的返回值。

    也可以指定属性名称

     1 @Controller  
     2 public class Hello2ModelController {  
     3       
     4     @ModelAttribute(value="myUser")  
     5     public User populateModel() {    
     6        User user=new User();  
     7        user.setAccount("ray");  
     8        return user;  
     9     }    
    10     @RequestMapping(value = "/helloWorld2")    
    11     public String helloWorld(Model map) {    
    12        return "helloWorld";    
    13     }    
    14 }  

    也可以将传递进来的对象与存在于Model中的对象进行合并,这时要将传递进来的对象名称改为与Model中进行合并的那个对象名一致

     1 @Controller  
     2 public class Hello2ModelController {  
     3       
     4     @ModelAttribute("myUser")  
     5     public User populateModel() {    
     6        User user=new User();  
     7        user.setAccount("ray");  
     8        return user;  
     9     }    
    10       
    11     @RequestMapping(value = "/helloWorld2")    
    12     public String helloWorld(@ModelAttribute("myUser") User user) {  
    13         user.setName("老王");  
    14        return "helloWorld";    
    15     }    
    16 }  

    这两种情况我们在jsp中访问要使用指定的属性名${myUser.name};

    需要注意的是:

    如果Model中没有key为user的属性,并且没写@ModelAttribute("user"),由于参数列表中有User user对象入参,则Spring会将该对象放入model,并且key值为首字母小写的类名,也就是说对于方法:

    1 @RequestMapping(value="/helloworld")
    2 public String helloWorld(User user)
    3 {
    4     return "helloworld";
    5 }

    框架提前帮你写了一句model.addAttribute("user",user)。
    而且特别需要注意注意,无论model中是否有key为user的属性,都要求User类有无参构造方法

    将SpringTest上的 @SessionAttributes 注解注释掉,添加如下方法

       //有 @ModelAttribute 标记的方法,会在每个目标方法执行之前被 SpringMVC 调用
      @ModelAttribute
    public void getUser(@RequestParam(value="id", required=false) Integer id, Map<String, Object> map) { System.out.println("modelAttribute method"); if(id != null) { User user = new User(1, "Tom", "1234", "beijing"); System.out.println("模拟从数据库删去一个对象:" + user); map.put("user", user); } } @RequestMapping("/testModelAttribute") public String testModelAttributes(User user) { System.out.println("修改:" + user); return SUCCESS; }
        <form action="springmvc/testModelAttribute" method="post">
            <input type="hidden" name="id" value="1"/>
            usernname: <input type="text" name="username" value="Tom"><br>
            address: <input type="text" name="address" value="beijing"><br>
            <input type="submit" value="submit">
        </form>

    在index.jsp页面address栏改为shanghai,运行程序控制台结果为

    modelAttribute method
    模拟从数据库删去一个对象:User [id=1, username=Tom, password=1234, address=beijing]
    修改:User [id=1, username=Tom, password=1234, address=shanghai]

    如果保留@SessionAttributes,并注释掉 getUser()方法,运行程序,此时程序会出现异常:

    org.springframework.web.HttpSessionRequiredException:Session attribute 'user' required - not found in session

    我们通过源码分析程序流程来找出异常产生的原因:

    1. 调用 @ModelAttribute 注解修饰的方法,实际上把方法中的 Map 中的数据放在了 implicitModel 中

    2. 解析请求处理器的目标参数,实际上该目标参数来自于 WebDataBinder 对象的 target 属性

    1) 创建 WebDataBinder 对象,它包括两个属性 objectName 和 target

    ① 确定 objectName 属性:若传入的 attrName 属性为 ""(空串),且目标方法的 POJO 参数未使用 @ModelAttribute 修饰,则objectName 为此 POJO 类名第一个字母小写

    注:若目标方法的 POJO 参数使用了 @ModelAttribute 来修饰,则 attrName 值即为 @ModelAttribute 的 value 属性值,如:

     //此时的 attrName 为 abc
     @RequestMapping("/testModelAttribute")
        public String testModelAttribute(@ModelAttribute("abc")User user)
        {
            System.out.println("修改:" + user);
            return "success";
        }

    ② 确定 target 属性:在 implicitModel 中查找 attrName 对应的属性值。

    若存在,获取到此属性值;(如在被 @ModelAttribute 修饰的方法中的 Map 保存过,且其 key 与 attrName 一致,则 target 值即为其 value 值)

    若不存在,则验证当前 Handler 是否使用了 @SessionAttributes 进行修饰。若使用了,则尝试从 Session 中获取 attrName 所对应的属性值,如果 session 中有与 attrName 对应的 key 但没有对应的属性,则抛出异常;若 Handler 没有使用 @SessionAttributes 进行修饰,或 @SessionAttributes 中没有使用 value 值指定的 key 与 attrName 匹配,则通过反射创建 POJO 对象,即创建了一个 target。

    所以前面程序之所以抛异常就是,@SessionAttributes 指定了 value="user",且 attrName 也为 user,二者匹配,但在 session 域内没有对应的属性值,所以抛出异常

    2) SpringMVC 把表单的请求参数赋给了 WebDataBinder 的 target 对应的属性

    3) SpringMVC 会把 WebDataBinder 的 attrName 和 target 给到 implicitModel,进而传递到请求域中

    4) 把 WebDataBinder 的 target 作为参数传递给目标方法的入参

     以上过程即可以看作为 SpringMVC 确定目标方法 POJO 类型参数入参的过程。

  • 相关阅读:
    TCP的三次握手与四次挥手
    HashMap源代码分析(JDK1.8)
    HashMap实现原理分析(JDK1.8前)
    codebook法分割前景目标
    平均场景法分割前景目标
    边缘梯度方向直方图的构建
    学习opencv 第六章 习题十三
    《学习OpenCV》 第四章 习题六
    《学习Opencv》第五章 习题6
    Opencv实现的简易绘图工具
  • 原文地址:https://www.cnblogs.com/2015110615L/p/5629461.html
Copyright © 2011-2022 走看看