Spring MVC从入门到精通—向页面带回响应数据的方法

1、SpringMVC 提供了以下几种途径输出模型数据:

  • ModelAndView: 处理方法返回值类型为 ModelAndView时, 方法体即可通过该对象添加模型数据
  • Map、Model以及ModelMap:入参为org.springframework.ui.Model、org.springframework.ui.ModelMap 或 Java.uti.Map 时,处理方法返回时,Map中的数据会自动添加到模型中。
  • @SessionAttributes: 将模型中的某个属性暂存到HttpSession 中,以便多个请求之间可以共享这个属性
  • @ModelAttribute: 方法入参标注该注解后, 入参的对象就会放到数据模型中。
  • 当然,除了上面这些SpringMVC提供的几种方法,SpringMVC支持直接使用Servlet几个原生API来给页面传值: HttpServletRequest requestHttpservletResponse responseHttpSession sessionInputStream/Reader 对应request.getInputStream()OutputStream/Writer 对应response.getOutputStram()

1.1 Servlet原生API给页面传值
   /**
     * 使用servlet原生API给页面输出数据
     * @param request
     * @param session
     * @return
     */
    @RequestMapping("handler01")
    public String handler01(HttpServletRequest request,
                            HttpSession session)
     throws IOException {

        request.setAttribute("msg","你好,这是HelloController");
        session.setAttribute("msg","json123");
        return "success";
    }

页面测试代码:success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>成功</title>
</head>
<body>
<center>
    <table border="1px" width="70%" >
        <tr>
            <th>域</th>
            <th>值</th>
        </tr>
        <tr>
            <th>requestScope</th>
            <td>${requestScope.msg}</td>
        </tr>
        <tr>
            <th>sessionScope</th>
            <td>${sessionScope.msg}}</td>
        </tr>
        <tr>
            <th>applicationScope</th>
            <td>${applicationScope.msg}</td>
        </tr>
        <tr>
            <th>pageScope</th>
            <td>${pageScope.msg}</td>
        </tr>
    </table>
</center>
</body>
</html>

测试结果:

1.2 Model、Map、ModelMap

首先通过通过源码看看他们三者的关系:
(1)ModelMap类

(2)Model接口

(3)ExtendModelMap类

(4)BindingAwareModelMap类

通过打开源码,我们不难总结出如下继承关系 :

接下来看看他们的用法:
示例代码

/**
     * 使用Model
     * @param model
     * @return
     */
    @RequestMapping("/handler02")
    public String handler02(Model model){

        System.out.println("Model"+model.getClass());
        model.addAttribute("msg","大家好!这是handler02");
        model.addAttribute("id",18);
        return "success";
    }


    /**
     * 使用Map
     * @param map
     * @return
     */
    @RequestMapping("/handler03")
    public String handler03(Map<String,String> map){
        System.out.println("Map:"+map.getClass());
        map.put("msg","handler03");
        map.put("logged","admin");
        return "success";
    }

    /**
     * 使用ModelMap
     * @param modelMap
     * @return
     */
    @RequestMapping("/handler04")
    public String handler04(ModelMap modelMap){
        System.out.println("ModelMap:"+modelMap.getClass());
        modelMap.addAttribute("msg","handler04");
        return "success";
    }

页面代码和上面样

测试结果
(1)页面的显示:

(2)控制台打印的信息:

从测试结果可以总结出:
Model(SpringMVC接口)其中一个实现类是ExtendedModelMap

ModelMap是Map(JDK的接口)Map的一个实现类,并且ModelMap被ExtendedModelMap
ExtendedModelMap被BindingAwareModelMap继承
Model、Map、ModelMap不论用哪个,最终工作的都是BindingAwareModelMap,而且从测试结果可以看到通过这三个设置的值,SpringMVC都把他们放在了request域中。

1.3 ModelAndView

目标方法的返回值可以是ModelAndView类型,从名字上就可以看到,这是一个既包括模型(Model)又有视图(View)的一个类, 然而事实也确实如此,他的model就可以理解为送给页面的数据,他的View可以理解为目标页面地址。但我们在他的model中放入值后,SpringMVC会把ModelAndView的model中数据放在request域对象中。

示例代码

     /**
     * 方法的返回值可以是 ModelAndView类型,这样我们可以把值设置在model中
     * 然后springmvc会把ModelAndView的model中数据放在request域对象中
     * @return
     */
    @RequestMapping("/handler05")
    public ModelAndView handler05(){

        ModelAndView mv=new ModelAndView("success");
        mv.addObject("msg","handler05");
        return mv;
    }

测试结果:

1.4 使用@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 的模型属性添加到会话中
总之:
当使用@SessionAttributes注解时就是告诉SpringMVC,当@SessionAttributes中的value值和BindingAwareModelMap的key一样时,那么在session也你也给我保存一份相同的值

示例代码:

//使用的时候一定要注意@SessionAttributes只能用在类上
@SessionAttributes(value={"id","logged"})
@Controller
public class HelloController {

    @RequestMapping("/handler0")
    public String sessionAttributesTest(Model model){

        model.addAttribute("msg","handler0");   //这个会在request中显示
        model.addAttribute("logged",new Date());   //会在session中显示
        model.addAttribute("id","001");         //会在session中显示
        return "success"; 
    }
}

页面代码对success.jsp中的sessionScope稍作修改:

 <tr>
     <th>sessionScope</th>
     <td>${sessionScope.msg} | ${sessionScope.id} |${sessionScope.logged}</td>
</tr>

测试结果:

1.5 使用@ModelAttribute注解

先来看看ModelAttribute的定义:

通过@ModelAttribute的定义可以看到这个注解可以用在方法和参数上。 在 SpringMVC 的 Controller 中使用 @ModelAttribute 时,应用情况包括下面几种:

1、应用在方法上。
2、应用在方法的参数上。
3、应用在方法上,并且方法也使用了@RequestMapping

示例代码:
修改图书信息的页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SpringMVC给页面输出数据</title>
</head>
<body>

<center>
<!--    <a href="handler01">原生API输出数据</a><br/>
    <a href="handler02">Model输出数据</a><br/>
    <a href="handler03">Map输出数据</a><br/>
    <a href="handler04">ModelMap输出数据</a><br/>
    <a href="handler05">ModelAndView带回返回值</a><br/>-->
    <h3>更新图书信息</h3>
    <form action="update" method="post">
        书名:<label>西游记</label><br/>
        作者:<label>吴承恩</label><br/>
        价格:<input type="text" name="price" placeholder="输入价格..."/><br/>
        库存:<input type="text" name="stock" placeholder="输入库存..."/><br/>
        销量:<input type="text" name="sales" placeholder="输入销量..."/><br/>
        <button type="submit">提交信息</button>
    </form>
</center>

</body>
</html>

提交图书修改信息后的页面:

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2019/8/7
  Time: 10:34
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>更新图书</title>
</head>
<body>
<center>
    <div style="height: 200px;width: 100%">
        <h3>提交的书籍的信息:</h3>
        <table border="1px" width="50%">
            <tr><th>书名</th><td>${book.name}</td></tr>
            <tr><th>作者</th><td>${book.author}</td></tr>
            <tr><th>价格</th><td>${book.price}</td></tr>
            <tr><th>库存</th><td>${book.stock}</td></tr>
            <tr><th>销量</th><td>${book.sales}</td></tr>
        </table>
    </div>
</center>

</body>
</html>

如果没有使用@ModelAttribute,那么要更新数据信息,必须要全字段更新,即使你不需要更新的的字段,你也要填写,这显然不和常理,因为如果你不填写这个值,值就会为null。最主要是因为SpringMVC在封装提交的信息的时候只会new一个Book对象,里面的属性的值初始就是null。你没有填写也只会以null存到数据库。
不使用@ModelAttribute进行非全字段更新

@Controller
public class BookController {
    @RequestMapping("/update")
    public String update(Book book){
        System.out.println("更新图书的信息......页面提交过来的图书信息:"+book);
        return "updateBook";
    }
 }

测试结果:
页面的显示:

看看控制台的打印信息:

可以看到果然不出预料的出问题了,更新信息后书名和作者的信息没了。这就相当于你更改了一下你的QQ密码,然后你的QQ号没了!这是很可怕的事情。
使用@ModelAttribute解决问题:

package com.xzy.Contorller;

import bean.Address;
import bean.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

@Controller
public class BookController {

    /**
     * 用在方法上:这个方法就会优先于该类中的左右处理器方法先执行
     * @param map
     */

    @ModelAttribute
    public void getBook(Map<String,Object> map){
        //模拟从数据库中查询图书数据
        Book book=new Book();
        book.setName("西游记");
        book.setPrice(9.98);
        book.setAuthor("吴承恩");
        book.setSales(300);
        book.setStock(400);
        System.out.println("数据库中查询到Book的信息:"+book);
        map.put("book",book);
        System.out.println("ModelAttribute将查询到的图书信息保存起来.......:");
    }
}

    /**
     * 可以告诉SpringMVC,你不要去new Book对象了,我已经从数据库中查询到了,你直接拿过去用就好了。
     * 问题是:如何告诉SpringMVC来用这个已经处理好的Book对象呢?
     * 这就是@ModelAttribute在参数位置的用法:
     * 下面的@ModelAttribute("book"),就是告诉SpringMVC,去拿一个key为
     * book的值,你不要重新new一个Book对象了,这样做的好处是可以只更改有更新的数据,没有更新的就保持原始值
     * @param book
     * @return
     */
    @RequestMapping("/update")
    public String update(@ModelAttribute("book") Book book){
        System.out.println("更新图书的信息......页面提交过来的图书信息:"+book);
        return "updateBook";
    }

测试结果:
页面展示的结果:

控制台打印的信息:

而且从控制台打印的信息来看,被@ModelAttribute标识的方法确实是在处理器方法之前执行了

1.6 @Modelattribute的原理

废话不多说,直接看代码

package com.xzy.Contorller;

import bean.Address;
import bean.Book;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

/**
 * @Author: HuangXin
 * @Date: Created in 10:33 2019/8/7
 * @Description:
 */
@Controller
public class BookController {

    private Object obj1;
    private Object b1;

    /**
     * 可以告诉SpringMVC,你不要去new Book对象了,我已经从数据库中查询到了,你直接拿过去用就好了。
     * 问题是:如何告诉SpringMVC来用这个已经处理好的Book对象呢?
     * 这就是@ModelAttribute在参数位置的用法:
     * 下面的@ModelAttribute("book"),就是告诉SpringMVC,你去拿一个key为book的值,
     * 你不要重新new一个Book对象了
     * @param book
     * @return
     */
    @RequestMapping("/update")
    public String update(@ModelAttribute("book") Book book, Map<String,Object> model){
        System.out.println("处理器方法的map:"+model.getClass());
        System.out.println("book==b1=>"+(book==b1));
        System.out.println("obj1==model=>"+(obj1==model));

        System.out.println("更新图书的信息......页面提交过来的图书信息:"+book);
        return "updateBook";
    }


    /**
     * 用在方法上:这个方法就会优先于该类中的左右处理器方法先执行
     *
     * @param map
     */

    @ModelAttribute
    public void getBook(Map<String,Object> map){
        //模拟从数据库中拿数据
        Book book=new Book();
        book.setName("西游记");
        book.setPrice(9.98);
        book.setAuthor("吴承恩");
        book.setSales(300);
        book.setStock(400);
        System.out.println("数据库中查询到Book的信息:"+book);

        obj1=map;
        b1=book;
        map.put("book",book);
        System.out.println("@ModelAttribute中的map:"+map.getClass());
        System.out.println("ModelAttribute将查询到的图书信息保存起来.......:");
    }

}

测试结果:

最后总结为一张图:

留言区

还能输入500个字符