第5章 处理响应数据
5.1 SpringMVC 输出模型数据概述
对于MVC框架来说模式数据是最重要的,因为控制C是为了产生模型数据M,而视图V则是为了渲染模型数据。
如何将模型数据暴露给视图是Spring MVC框架的一项重要工作,Spring MVC提供了多种途径输出模型数据
- ModelAndView: 处理方法返回值类型为 ModelAndView 时, 方法体即可通过该对象添加模型数据
- @ModelAttribute:方法入参标准该注解后,入参的对象就会放到数据模型中
- Map及Model: 入参为 org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 时,处理方法返回时,Map 中的数据会自动添加到模型中。
- @SessionAtributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性。
5.2 处理模型数据之ModelAndView
5.2.1 ModelAndView介绍
1)控制器处理方法的返回值如果为 ModelAndView,则其既包含视图信息,也包含模型数据信息。这样Spring MVC就可以使用视图对模型数据进行渲染了。可以简单地将模型数据看成一个Map<String,Object>对象
构造器的有参无参 有参写法:ModelAndView mv = new ModelAndView(viewName );无参写法:ModelAndView mav = new ModelAndView();mav.setViewName("success");//设置视图信息
2)添加模型数据:
MoelAndView addObject(String attributeName, Object attributeValue)
ModelAndView addAllObject(Map<String, ?> modelMap)
3)设置视图:
void setView(View view)//指定一个具体的视图对象
void setViewName(String viewName)//指定一个逻辑视图名//返回逻辑地址
5.2.2 实验代码
1) 增加控制器方法
/*** 目标方法的返回类型可以是ModelAndView类型* 其中包含视图信息和模型数据信息 结论: Springmvc会把ModelAndView中的模型数据存放到request域对象中.*/
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){System.out.println("testModelAndView");String viewName = "success";//设置到了ModelAndView中ModelAndView mv = new ModelAndView(viewName );mv.addObject("time",new Date().toString()); //实质上存放到request域中 return mv;
}/* @ModelAttribute的用法:
Spring MVC将请求消息绑定到User对象中,然后再以"user:user对象"为键值对放到模型中。在准备对视图进行渲染前,Spring MVC还会进一步将模型中的数据转储到视图的上下文中以暴露给视图对象。对于JSP视图来说,Spring MVC会将模型数据转储到ServletRequest的属性列表中(即通过ServletRequest#setAttribute(String name,Object o)保存)*/
@RequestMapping("/testModelAndView1")
public ModelAndView testModelAndView1(@ModelAttribute("user") User user){System.out.println("testModelAndView1");return "success";
}
/*也可以在方法上标@ModelAttribute,此时Spring MVC在调用目标处理方法前,会先逐个调用在方法级上标准了@ModelAttribute的方法,并将这些方法的返回值添加到模型中。*/
@ModelAttribute("user")//返回的健名
public User getUser(){User user = new User();user.setUseId("1001");return user;
}
@RequestMapping("/testModelAndView2")
public ModelAndView testModelAndView2(@ModelAttribute("user") User user){System.out.println("testModelAndView2");return "success";
}
2) 增加页面链接
<!--测试 ModelAndView 作为处理返回结果 -->
<a href="springmvc/testModelAndView">testModelAndView</a>
3) 增加成功页面,显示数据
time: ${requestScope.time }
4) 断点调试
5.2.2 源码解析
5.3 处理模型数据之 Map
5.3.1 Map介绍
Spring MVC在内部使用了
1)Spring MVC 在内部使用了一个 org.springframework.ui.Model 接口存储模型数据,他的功能类似于java.util.Map,但它比Map易用。org.springframework.ui.ModelMap实现了Map接口,而org.springframework.ui.ExtendedModelMap扩展于ModelMap同时实现了Model接口。
2)Spring MVC在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。
3)如果方法的入参为 Map或 Model类型,Spring MVC 会将隐含模型的引用传递给这些入参。
4)在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据
Spring MVC在调用方法之前会创建一个隐含的模型对象,作为模型数据的存储容器,我们称之为“隐含模型”。如果处理方法的入参为Map或Model模型,Spring MVC会将隐含模型的引用传递给这些入参。在方法体内,开发者可以同这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据。
5.3.2 实验代码
1) 增加控制器方法
//目标方法的返回类型也可以是一个Map类型参数(也可以是Model,或ModelMap类型)
@RequestMapping("/testMap")
public String testMap(Map<String,Object> map){ //【重点】System.out.println(map.getClass().getName());//org.springframework.validation.support.BindingAwareModelMapmap.put("names", Arrays.asList("Tom","Jerry","Kite"));return "success";
}
2) 增加页面链接
<!-- 测试 Map 作为处理返回结果 -->
<a href="springmvc/testMap">testMap</a>
3) 增加成功页面,显示结果
names: ${requestScope.names }
4) 显示结果截图
5) 注意问题:Map集合的泛型,key为String,Value为Object,而不是String
6) 测试参数类型
//目标方法的返回类型也可以是一个Map类型参数(也可以是Model,或ModelMap类型)
@RequestMapping("/testMap2")
public String testMap2(Map<String,Object> map,Model model,ModelMap modelMap){System.out.println(map.getClass().getName());map.put("names", Arrays.asList("Tom","Jerry","Kite"));model.addAttribute("model", "org.springframework.ui.Model");modelMap.put("modelMap", "org.springframework.ui.ModelMap");System.out.println(map == model);System.out.println(map == modelMap);System.out.println(model == modelMap);System.out.println(map.getClass().getName());System.out.println(model.getClass().getName());System.out.println(modelMap.getClass().getName());/*truetruetrueorg.springframework.validation.support.BindingAwareModelMaporg.springframework.validation.support.BindingAwareModelMaporg.springframework.validation.support.BindingAwareModelMap*/ return "success";
}
7) 类层次结构
8) 推荐:Map, 便于框架移植。
9) 源码参考
public class BindingAwareModelMap extends ExtendedModelMap {@Overridepublic Object put(String key, Object value) {removeBindingResultIfNecessary(key, value);return super.put(key, value);}@Overridepublic void putAll(Map<? extends String, ?> map) {for (Map.Entry<? extends String, ?> entry : map.entrySet()) {removeBindingResultIfNecessary(entry.getKey(), entry.getValue());}super.putAll(map);}private void removeBindingResultIfNecessary(Object key, Object value) {if (key instanceof String) {String attributeName = (String) key;if (!attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attributeName;BindingResult bindingResult = (BindingResult) get(bindingResultKey);if (bindingResult != null && bindingResult.getTarget() != value) {remove(bindingResultKey);}}}}
}
放到session里:
在控制器(类)上加@SessionAttributes(value={“user”}),既放在session里面,也放在请求域里面
Model
/*** Model*/@RequestMapping("/testModel")public String testModel(Model model) {//模型数据 : loginMsg=用户名或者密码错误model.addAttribute("loginMsg", "用户名或者密码错误");return "success";}
@ SessionAttributes
如果希望在多个请求之间共用某个模型属性数据,则可以在控制器类标注一个@SessionAttributes,Spring MVC会将模型中对应的属性暂存到HttpSession中。
在类上标准@SessionAttributes(“user”),然后类中某个方法的参数是@ModelAttribute("user") User user
,此时就会把这个方法中的参数变为全局都可以获得的参数,别的方法获取方式为modelMap.get("user");
整合代码
package com.atguigu.springmvc.handler;import java.util.Map;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
public class SpringmvcHandler {/*** 重定向*/@RequestMapping("/testRedirectView")public String testRedirectView() {return "redirect:/ok.jsp";}/*** 视图 View*/@RequestMapping("/testView")public String testView() {return "success";}/*** Model*/@RequestMapping("/testModel")public String testModel(Model model) {//模型数据 : loginMsg=用户名或者密码错误model.addAttribute("loginMsg", "用户名或者密码错误");return "success";}/*** Map* 结论: SpringMVC会把Map中的模型数据存放到request域对象中.* SpringMVC再调用完请求处理方法后,不管方法的返回值是什么类型,都会处理成一个ModelAndView对象(参考DispatcherServlet的945行)* */ @RequestMapping("/testMap")public String testMap(Map<String,Object> map ) {//模型数据: password=123456System.out.println(map.getClass().getName()); //BindingAwareModelMapmap.put("password", "123456");return "success";}/*** ModelAndView* 结论: Springmvc会把ModelAndView中的模型数据存放到request域对象中.*/@RequestMapping("/testModelAndView")public ModelAndView testModelAndView() {//模型数据: username=AdminModelAndView mav = new ModelAndView();//添加模型数据mav.addObject("username", "Admin");//设置视图信息mav.setViewName("success");return mav ;}
}
使用spring ResponseEntity处理http响应
简介
使用spring时,达到同一目的通常有很多方法,对处理http响应也是一样。本文我们学习如何通ResponseEntity设置http相应内容、状态以及头信息。
ResponseEntity
ResponseEntity标识整个http相应:状态码、头部信息以及相应体内容。因此我们可以使用其对http响应实现完整配置。
如果需要使用ResponseEntity,必须在请求点返回,通常在spring rest中实现。ResponseEntity是通用类型,因此可以使用任意类型作为响应体:
@GetMapping("/hello")
ResponseEntity<String> hello() {return new ResponseEntity<>("Hello World!", HttpStatus.OK);
}
可以通过编程方式指明响应状态,所以根据不同场景返回不同状态:
@GetMapping("/age")
ResponseEntity<String> age(@RequestParam("yearOfBirth") int yearOfBirth) {if (isInFuture(yearOfBirth)) {return new ResponseEntity<>("Year of birth cannot be in the future", HttpStatus.BAD_REQUEST);}return new ResponseEntity<>("Your age is " + calculateAge(yearOfBirth), HttpStatus.OK);
}
另外,还可以设置http响应头:
@GetMapping("/customHeader")
ResponseEntity<String> customHeader() {HttpHeaders headers = new HttpHeaders();headers.add("Custom-Header", "foo");return new ResponseEntity<>("Custom header set", headers, HttpStatus.OK);}
而且, ResponseEntity提供了两个内嵌的构建器接口: HeadersBuilder 和其子接口 BodyBuilder。因此我们能通过ResponseEntity的静态方法直接访问。
最简单的情况是相应包括一个主体及http 200响应码:
@GetMapping("/hello")
ResponseEntity<String> hello() {return ResponseEntity.ok("Hello World!");
}
大多数常用的http 响应码,可以通过下面static方法:
BodyBuilder accepted();
BodyBuilder badRequest();
BodyBuilder created(java.net.URI location);
HeadersBuilder<?> noContent();
HeadersBuilder<?> notFound();
BodyBuilder ok();
另外,可以能使用BodyBuilder status(HttpStatus status)和BodyBuilder status(int status) 方法设置http状态。使用ResponseEntity BodyBuilder.body(T body)设置http响应体:
@GetMapping("/age")
ResponseEntity<String> age(@RequestParam("yearOfBirth") int yearOfBirth) {if (isInFuture(yearOfBirth)) {return ResponseEntity.badRequest().body("Year of birth cannot be in the future");}return ResponseEntity.status(HttpStatus.OK).body("Your age is " + calculateAge(yearOfBirth));
也可以自定义头信息:
@GetMapping("/customHeader")
ResponseEntity<String> customHeader() {return ResponseEntity.ok().header("Custom-Header", "foo").body("Custom header set");
}
因为BodyBuilder.body()返回ResponseEntity 而不是 BodyBuilder,需要最后调用。注意使用HeaderBuilder 不能设置任何响应体属性。
尽管ResponseEntity非常强大,但不应该过度使用。在一些简单情况下,还有其他方法能满足我们的需求,使代码更整洁。
替代方法
@ResponseBody
典型spring mvc应用,请求点通常返回html页面。有时我们仅需要实际数据,如使用ajax请求。这时我们能通过@ResponseBody注解标记请求处理方法,审批人能够处理方法结果值作为http响应体。
@ResponseStatus
当请求点成功返回,spring提供http 200(ok)相应。如果请求点抛出异常,spring查找异常处理器,由其返回相应的http状态码。对这些方法增加@ResponseStatus注解,spring会返回自定义http状态码。
直接操作相应
Spring 也允许我们直接 javax.servlet.http.HttpServletResponse 对象;只需要申明其作为方法参数:
@GetMapping("/manual")
void manual(HttpServletResponse response) throws IOException {response.setHeader("Custom-Header", "foo");response.setStatus(200);response.getWriter().println("Hello World!");
}
但需要说明,既然spring已经提供底层实现的抽象和附件功能,我们不建议直接操作response。