SpringMVC——请求数据传入&响应数据传出和@ModelAttribute注解
一、请求数据传入
1.1 请求处理方法签名
- Spring MVC 通过分析处理方法的签名,HTTP请求信息绑定到处理方法的相应人参中。
- Spring MVC 对控制器处理方法签名的限制是很宽松的,几乎可以按喜欢的任何方式对方法进行签名。
- 必要时可以对方法及方法入参标注相应的注解( @PathVariable 、@RequestParam、@RequestHeader 等)、
- Spring MVC 框架会将 HTTP 请求的信息绑定到相应的方法入参中,并根据方法的返回值类型做出相应的后续处理。
//访问路径:/handle02?username=zhangsan
//默认方式获取请求参数: 直接给方法入参上写一个和请求参数名相同的变量。这个变量就来接收请求参数的值;没有值则为null
@RequestMapping("/handle02")
public String handle02(String username){
System.out.println("这个变量的值:"+username);//这个变量的值:zhangsan
return "success";
}
1.2 @RequestParam注解
-
在处理方法入参处使用
@RequestParam
可以把请求参数传递给请求方法 -
三个属性:
value:参数名 required:是否必须。默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常 defaultValue: 默认值,当没有传递参数时使用该值
/**
* @RequestParam 注解用于映射请求参数
* value 用于映射请求参数名称
* required 用于设置请求参数是否必须的,默认为 true表示请求参数中必须包含对应的参数,
* 若不存在,将抛出异常Required String parameter 'user' is not present
* defaultValue 设置默认值,当没有传递参数时使用该值
*/
@RequestMapping("/handle03") //访问路径:handle03?username=zhangsan
public String handle03(@RequestParam(value="user",required=false,defaultValue="你没带")String username){
System.out.println("这个变量的值:"+username);//输出:这个变量的值:你没带
return "success";
}
1.3 @RequestHeader 注解
- 请求头包含了若干个属性,服务器可据此获知客户端的信息,通过
@RequestHeader
即可将请求头中的属性值绑定到处理方法的入参中
/**
* @RequestHeader:获取请求头中某个key的值;
* 以前获取请求头中某个key的值:request.getHeader("User-Agent");
* @RequestHeader("User-Agent")String userAgent ,如果请求头中没有这个值就会报错;
* 等同于:userAgent = request.getHeader("User-Agent")
*
* 同样可以设置value、required、defaultValue参数
*/
@RequestMapping("/handle04")
public String handle04(@RequestHeader(value="user-Agent",required=false)String userAgent){
System.out.println("请求头中浏览器的信息:"+userAgent);
return "success";
}
1.4 @CookieValue注解
@CookieValue
可让处理方法入参绑定某个 Cookie 值
/**
* @CookieValue:获取某个cookie的值; 以前的操作获取某个cookie;
* Cookie[] cookies = request.getCookies();
* for(Cookie c:cookies){
* if(c.getName().equals("JSESSIONID")){
* String cv = c.getValue();
* }
* }
* 同样可以设置value、required、defaultValue参数
*/
@RequestMapping("/handle05")
public String handle05(@CookieValue(value="JSESSIONID",required=false)String jid){
System.out.println("cookie中的jid的值:"+jid);
return "success";
}
1.5 使用POJO作为参数
- 使用 POJO 对象绑定请求参数值
- Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。 支持级联属性。如:dept.deptId、dept.address.tel 等
控制器方法:
/**
* 如果我们的请求参数是一个POJO;
* SpringMVC会自动的为这个POJO进行赋值?
* 1)、将POJO中的每一个属性,从request参数中尝试获取出来,并封装即可;(通过set方法安赋值)
* 2)、还可以级联封装;属性的属性
* 3)、请求参数的参数名和对象中的属性名一一对应就行
*/
@RequestMapping("/book")
public String addBook(Book book,String bookName){
System.out.println("图书的信息是:"+book);//将对象中属性名跟请求参数的参数名对应的直接赋值
return "success";
}
实体类:
form表单:
<form action="book" method="post"><br/>
书名:<input type="text" name="bookName"/><br/>
作者:<input type="text" name="author"/><br/>
价格:<input type="text" name="price"/><br/>
库存:<input type="text" name="stock"/><br/>
销量:<input type="text" name="sales"/><br/>
省:<input type="text" name="address.province"/><br/><!-- 级联属性 -->
市:<input type="text" name="address.city"/><br/>
街道:<input type="text" name="address.street"/><br/>
<input type="submit" value="提交"/>
</form>
1.6 使用Servlet原生API作为参数
MVC 的 Handler 方法可以接受哪些 ServletAPI 类型的参数
HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer
/**
* SpringMVC可以直接在参数上写原生API;
*
* HttpServletRequest
* HttpServletResponse
* HttpSession
*
* java.security.Principal
* Locale:国际化有关的区域信息对象
* InputStream:
* ServletInputStream inputStream = request.getInputStream();
* OutputStream:
* ServletOutputStream outputStream = response.getOutputStream();
* Reader:
* BufferedReader reader = request.getReader();
* Writer:
* PrintWriter writer = response.getWriter();
*
* @throws IOException
*/
@RequestMapping("/handle06")
public String handle06(HttpSession session,HttpServletRequest req){
//可以再请求转发的页面获取request域和session域中的数据
req.setAttribute("reqParam","我是requset域中的值");
session.setAttribute("sessionParam", "我是session域中的值");
return "success";
}
1.7 SpringMVC乱码处理
之前对于中文乱码的处理:
* 提交的数据可能有乱码:
* 请求乱码:
* GET请求:改server.xml;在8080端口处URIEncoding="UTF-8"
* POST请求:
* 在第一次获取请求参数之前设置
* request.setCharacterEncoding("UTF-8");
*
* 响应乱码:
* response.setContentType("text/html;charset=utf-8")
而SpringMVC中如果出现中文乱码,需要配置字符编码过滤器
,且配置其他过滤器之前。如(HiddenHttpMethodFilter),否则不起作用。
在web.xml中配置:
<!-- 配置一个字符编码的Filter;一定注意:字符编码filter一般都在其他Filter之前; -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!-- encoding:指定解决post请求乱码 -->
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<!-- forceEncoding:解决响应乱码问题 -->
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
二、响应数据传出
Spring MVC提供了以下几种途径输出模型数据:
ModelAndView
—— 控制器的处理方法返回值类型为ModelAndView时,在处理方法中就可以通过该对象添加模型数据,然后输出。Map、Model及ModelMap
—— 入参为Model、ModelMap或Map时,处理方法返回时,Map中的数据会自动添加到模型中,然后输出。@SessionAttributes
—— 将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性。
2.1 处理模型数据之 Map、Model和ModelMap)
可以在方法处传入Map、或者Model或者ModelMap。给这些参数里面保存的所有数据都会放在请求域中。可以在页面获取
具体使用步骤:
- Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。
如果方法的入参为 Map 或 Model 类型
,Spring MVC 会将隐含模型的引用传递给这些入参。- 在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据
控制器方法:
/**
* 可以在方法处传入Map、或者Model或者ModelMap。
* 给这些参数里面保存的所有数据都会放在请求域中。可以在页面获取
*/
@Controller
public class OutputController {
//参数为Map类型
@RequestMapping("/handle01")
public String handle01(Map<String,Object> map){
//org.springframework.validation.support.BindingAwareModelMap
System.out.println("map的类型"+map.getClass());
map.put("msg","message信息");//可以再request域中获取数据
return "success";
}
//参数为Model类型
@RequestMapping("/handle02")
public String handle02(Model model){
System.out.println("model的类型"+model.getClass());
model.addAttribute("msg", "message信息");
return "success";
}
//参数为ModelMap类型
@RequestMapping("/handle03")
public String handle03(ModelMap modelMap){
System.out.println("modelMap的类型"+modelMap.getClass());
modelMap.addAttribute("msg", "message信息");
return "success";
}
}
跳转页面可在request域中获取Map、或者Model或者ModelMap中的数据
获取map中的数据:${requestScope.msg }<br/>
- Map、Model、ModelMap、BindingAwareModelMap之间的关系:
推荐:Map, 便于框架移植
2.2 处理模型数据之 ModelAndView
- 控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息,也包含模型数据信息。
- 添加模型数据:
MoelAndView addObject(String attributeName, Object attributeValue)
ModelAndView addAllObject(Map<String, ?> modelMap) - 设置视图:
void setView(View view)
void setViewName(String viewName)
控制器方法:
/**
* 方法的返回值可以变为ModelAndView类型;
* 既包含视图信息(页面地址)也包含模型数据(给页面带的数据);
* 而且数据是放在请求域中;
*/
@RequestMapping("/handle04")
public ModelAndView handle04(){
//之前的返回值我们就叫视图名;视图名视图解析器是会帮我们最终拼串得到页面的真实地址;
// ModelAndView mv = new ModelAndView("success");//可以使用有参构造器传入视图名
ModelAndView mv = new ModelAndView();
mv.setViewName("success");
mv.addObject("msg", "message信息");//实质上存放到request域中
return mv;
}
2.3 处理模型数据之 SessionAttributes 注解
- 若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个
@SessionAttributes
, Spring MVC 将在模型中对应的属性暂存到HttpSession
中。 - @SessionAttributes 除了可以通过
属性名
指定需要放到会话中的属性外,还可以通过模型属性的对象类型
指定哪些模型属性需要放到会话中
例如:
@SessionAttributes(types=User.class) 会将隐含模型中所有类型为 User.class 的属性添加到会话中。
value={"msg"}:只要保存的是这种key的数据,给Session中放一份
types={String.class}:只要保存的是这种类型的数据,给Session中也放一份
/**
* SpringMVC提供了一种可以临时给Session域中保存数据的方式;
* 使用一个注解 @SessionAttributes(只能标在类上)
* 例如:
* @SessionAttributes(value="msg"):
* 给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据,同时给session中放一份;
* value指定保存数据时要给session中放的数据的key;
*
* 两个属性:
* value={"msg"}:只要保存的是这种key的数据,给Session中放一份
* types={String.class}:只要保存的是这种类型的数据,给Session中也放一份
*
* 后来推荐@SessionAttributes就别用了,可能会引发异常;
* 给session中放数据请使用原生API;
* @return
*/
//@SessionAttributes(value={"msg","key"})//只要保存的key的值是msg和key,就给session中也放一份
@SessionAttributes(types={
String.class})//只要保存的是这种类型的数据,给Session中也放一份
@Controller
public class OutputController {
@RequestMapping("/handle05")
public String handle05(Map<String,Object> map){
//默认是被存放到request 域,如果设置了@SessionAttribute注解,就同时存放到session域中
map.put("msg","message信息");
map.put("key", "字符串类型");
return "success";
}
}
三、@ModelAttribute注解(了解)
以下转载自https://blog.csdn.net/sinat_28978689/article/details/65627501
好的博客:
@ModelAttribute注解的使用总结
3.1 问题的引入
假设我们需要修改一个User对象,但是我们规定某一个或者几个字段不能修改,我们会利用表单填写相关信息,然后提交给后台,然后 在后台会new出一个对象,并将表单提交的数据赋值到这个表单的属性中,然后执行update操作,但是由于有些字段不能被修改,所以我们在前台传递的对象有些属性为空,所以在更新的时候,那些不能被修改的字段就会被默认为空值。
解决:1、我们可以利用隐藏域,在用户进行修改的时候,将对应的不能修改的字段进行隐藏,这样的话,可以解决,但是如果对应的字段的敏感度比较高,例如密码不适合利用隐藏域进行记录,这种方法是不合适的。
2、我们可以在数据更新之前,先从数据库中将用户信息给读取出来,然后将不能修改的字段重新进行赋值然后更新。 但是如果所不能修改的字段比较多,我们就会对这个比较麻烦。重复代码较多。
ModelAttribute注解引出:
对于上述两种方式都有对应的缺点,但是对于更新数据比较小的可以使用,所以我们可以利用我们的这个ModelAttribute注解进行处理。
注意 :这里的对象不是new出来的,而是从数据库获取出来的,这就是ModelAttribute的作用之一。
在没有利用@ModelAttribute修饰前演示
<!-- POJO类ModelAttributes属性 -->
private int id;
private String name;
private int age;
private String email;
private String password; // 不能被修改
<!--
模拟修改操作 前台页面
1.原始数据为 1 tom 12 ff@163.com 12345
2.密码不能修改
3.表单回显 ,模拟操作直接在表单填写对应的属性值
在用户修改数据时,有些数据不能修改,所以在修改时我们把这些子段不给显示,
我们应该是从数据库中先获取,然后 利用ModelAttribute注解进行 覆盖,
会比我们自己手动利用覆盖的效果高
-->
<form action="TestModelArributes" method="post" >
<input type="hidden" name="id" value="1">
name:<input type="text" name="name" value="tom">
age:<input type="text" name="age" value="12" >
email:<input type="text" name="eamil" value="[email protected]" >
<input type="submit" value="submit">
</form>
<!-- 后台处理 -->
@RequestMapping("/TestModelArributes")
public String TestModelArributes(User user){
System.out.println("修改:"+user);
// 执行更新操作 省略
return SUCCESS;
}
<!-- 控制台打印结果 -->
修改:User[id=1, name=tom, age=13, email=ff@163.com, password=null]
注意:在这里我们的原始数据 带有密码,但是从前台到后台处理的时候,这个密码由于没有在表单之中,所以传过来的POJO 的密码为空。 即利用方法一所造成的。
然后我们在后台处理中新增加一个方法,注意是被@ModelAttribute修饰的。
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
Map<String, Object> maps){
if(id != null){
// id不为空,说明是用户执行了修改操作
// 模拟从数据库中获取对象 我们是生成的
User user =new User(1,"tom", 12,"[email protected]", "1234");
System.out.println("从数据库中获取一个对象:"+user );
maps.put("user", user);
}
}
<!-- 控制台打印结果 -->
从数据库中获取一个对象:User[id=1, name=tom, age=12, email=ff@163.com, password=1234]
修改:User[id=1, name=tom, age=13, email=ff@163.com, password=1234]
当被@ModelAttribute修饰一个方法之后,结果就变成了password有值了,而且也应该注意到,被@ModelAttribute修饰的方法给提前执行了,这就是@ModelAttribute的作用。这样的话,再做修改操作的话,就能将原始密码给带进入修改操作了。为什么被@ModelAttribute修饰一个方法之后就会有这样的结果呢?
下面我们抽出主要代码进行分析:
<!-- 第一种方式 -->
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
Map<String, Object> maps){
if(id != null){
User user =new User(1,"tom", 12,"[email protected]", "1234");
maps.put("user", user);
}
}
@RequestMapping("/TestModelArributes")
public String TestModelArributes(User user){
System.out.println("修改:"+user);
return SUCCESS;
}
=============================
<!--第二种方式 -->
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
Map<String, Object> maps){
if(id != null){
User user=new User(1,"tom", 12, "1234");
maps.put("change", user);
}
}
@RequestMapping("/TestModelArributes")
public String TestModelArributes(@ModelAttribute( value="change") User user){
System.out.println("修改:"+user);
return SUCCESS;
}
注意
1、@ModelAttribute修饰的方法会被Springmvc提前执行,运行在每个目标方法之前。
2、在@ModelAttribute修饰的方法中,【map存入的键需要和目标方法入参类型的第一个字母小写的字符串一致】方式一所示,否则的话,在目标的请求参数方法中不能匹配到从数据库中提取出来的数据,或者就是你也将目标方法的参数用利用注解ModelAttribute修饰且设置value属性值指向你的map中的键名,上述代码的第二种方式
3.2 分析流程
运行流程:
我们上述代码可以分为三个步骤:
1、 执行ModelArribute修饰的方法,从数据库中取出对象,把对象放到Map 中,键为user。
2、springmvc 从Map 中取出User 对象 ,并把表单的请求参数赋给该User对象的对应属性。
3、springmvc 把上述对象传入到目标方法的参数。
3.2.1 @ModelAttribute源码分析流程
源码分析流程:对应上面的运行流程。
1、调用@ModelAttribute 注解修饰的方法,实际上把@ModelAttribute方法中 Map中的数据放在了implicitModel中。
2、解析请求处理器的目标参数,实际上该目标参数来自于WebDataBinder对象的target属性。
2.1、创建WebDataBinder对象。
a) 确定objectName属性:若传入的attrName属性值为空”“,则objectName为类名第一个字母小写。需要注意的是:attrName。如果目标方法的POJO参数属性使用了@ModelAttribute来修饰,则attrName值即为@ModelAttribute的value属性值(对应我们的第二种写法)
b)确定target属性:在implicitModel 中查找attrName对应的属性值,如果存在,进行利用。如果不存在,则验证当前Handler是否使用了@SessionAttribute进行修饰类。如果使用了@SessionAttribute,则尝试从HttpSession中获取attrName所对应的值,若Session中没有所对应的值,则会抛出异常。【这也是SessionAttribute与ModelAttribute共同使用时造成的一个常见问题】,如果当前Handler没有使用@SessionAttribute修饰类,且@SessionAttribute中没有使用value值指定的key和attrName相匹配,则通过反射机制进行创建POJO 对象。
2.2、Springmvc把表单的请求参数赋值给WebDataBinder 的target对应的属性。
2.3、Springmvc 会把WebDataBinder的attrName和target给到implicitModel,进而传递到request域对象中。
3、把WebDataBinder的target作为参数传递给目标方法。
3.2.2 SpringMVC 确定目标方法 POJO 类型入参的过程
① 确定一个 key:
1). 若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写
2). 若使用了@ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值.
② 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
1). 若在 @ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 ① 确定的 key 一致, 则会获取到.
③ 若 implicitModel 中不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰,
④ 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所对应的 value 值, 若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常.
⑤ 若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数
⑥ SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而会保存到 request 中.
3.3 使用总结
1、注解在没有返回值的方法上面
就如上面的例子注解在没有返回值的方法上面,用在POJO入参上面,或者我们可以在这里设定一些Request域中的对象,在前台可以直接使用
@ModelAttribute
public void getUser(){
maps.put("name", "zhangsan");
}
2、注解在有返回值的方法上面
这里要用value属性进行数据存储,会将返回值存储在value属性的值中,可以在前台直接获取。
@ModelAttribute(value="user00")
public User testModelAttribute(){
User user =new User(1,"tom00", 12,"[email protected]", "123400");
return user;
}
3、在方法参数上使用@ModelAttribute
在方法的参数中使用必须是修饰POJO类,这样的话,才能从相应的域中获取数据
@RequestMapping("/TestModelArributes")
public String TestModelArributes(@ModelAttribute( value="change") User user){
System.out.println("打印"+user);
return SUCCESS;
}
这样的话,会从implicitModel 隐藏域中查询此类型对象,就犹如上面分析的传递POJO流程,其实这里的作用只是可以使用change标记我们的user对象。