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 request
、HttpservletResponse response
、HttpSession session
、InputStream/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类
通过打开源码,我们不难总结出如下继承关系 :

接下来看看他们的用法: 示例代码
/**
* 使用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";
}
页面代码和上面样
从测试结果可以总结出: 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将查询到的图书信息保存起来.......:");
}
}