一、控制器接收各类请求参数
代码测试环境:
接收各类参数的控制器--ParamsController
package com.ssm.chapter15.controller; @Controller @RequestMapping("/params") public class ParamsController { // 各种控制器方法 }
先看一下目录结构:
这里需要知道的知识点是,WebContent文件夹下的.jsp文件都可以通过http://localhost:8080/工程名/文件名.jsp直接访问。
而WEB-INF里面的文件,必须通过Spring MVC 中的Controller控制器产生映射才能访问。
1.接收普通请求参数
params.jsp文件的内容如下,其中action="./params/commonParams.do"表示提交按钮按下后,跳转到action指定的页面。
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>参数</title> <body> <!-- 根据你的需要改变请求url --> <form id="form" action="./params/commonParams.do"> <table> <tr> <td>角色名称</td> <td><input id="roleName" name="roleName" value="" /></td> </tr> <tr> <td>备注</td> <td><input id="note" name="note" /></td> </tr> <tr> <td></td> <td align="right"><input type="submit" value="提交" /></td> </tr> </table> </form> </body> </html>
而对应的控制器方法:commonParams方法
@Controller @RequestMapping("/params") public class ParamsController { @RequestMapping("/commonParams") public ModelAndView commonParams(String roleName, String note) { System.out.println("roleName =>" + roleName); System.out.println("note =>" + note); ModelAndView mv = new ModelAndView(); mv.setViewName("index"); return mv; }
这里因为当前Spring MVC 比较智能化,如果传递进来的参数名称和HTTP的保存一致,意思就是传递进来的参数名称为roleName和note,而params.jsp中<input id="roleName" name="roleName" value="" />和<input id="note" name="note" />两个参数名称都和roleName和note一致,因此,可以获取到params.js中提交的参数。
测试:首先输入访问表单,输入任意参数,并提交
然后,正确跳转到./params/commonParams.do?roleName=mingcheng¬e=beizhu,这一URL,说明参数传递成功。
但是,在参数很多的情况下,再使用这样的方式,显然所写方法的参数就会非常多,这是应该考虑到使用一个POJO来管理这些参数。在没有任何注解的情况下,Spring MVC 也有映射POJO的能力。
新建一个角色参数类,将两个参数封装到类中:
package com.ssm.chapter14.pojo; public class RoleParams { private String roleName; private String note;
/*getter and setter*/ }
然后增加控制器方法:由于上面的POJO的属性和HTTP参数(jsp文件中的参数)一一对应了,然后在commonParamPojo方法中将POJO类对象RoleParams roleParams当成方法的参数,也可以在没有任何注解的情况下实现参数的有效传递。
@RequestMapping("/commonParamPojo") public ModelAndView commonParamPojo(RoleParams roleParams) { System.out.println("roleName =>" + roleParams.getRoleName()); System.out.println("note =>" + roleParams.getNote()); ModelAndView mv = new ModelAndView(); mv.setViewName("index"); return mv; }
另外,还需要修改jsp中的action成<form id="form" action="./params/commonParamPojo.do">才能进行测试。
2.使用@RequestParam注解获取参数
前面的两种情况,仅仅在参数名称和jsp文件中的参数名称一一对应时才有效。那么,如果修改jsp中的参数名称,例如,<td><input id="role_name" name="role_name" value="" /></td>将roleName修改成role_Name,此时由于参数不一致,就无法再进行自动对应传递了。
可以用@RequestParam注解获取参数的方式解决这个问题:使用@RequestParam("role_name")来讲HTTP的参数名称(即jsp文件中的参数名称)和传递进去的roleName参数一一对应。
@RequestMapping("/requestParam") //使用@RequestParam("role_name")指定映射HTTP参数名称 public ModelAndView requestParam(@RequestParam("role_name") String roleName, String note) { System.out.println("roleName =>" + roleName); System.out.println("note =>" + note); ModelAndView mv = new ModelAndView(); mv.setViewName("index"); return mv; }
同样,修改action,然后也可以得到正确的测试结果:
3.使用URL传递参数
一些网站使用URL的形式传递参数,对于一些业务比较简单的应用是非常常见的,如果想把获得数据库中id为1的role的信息,那么就写成/params/getRole/1,这里的1就代表角色编号,只不过是在URL中传递。Spring MVC 也提供了支持。
需要通过@RequestMapping注解和@PathVariable注解协作完成。其中,
@RequestMapping("/getRole/{id}")中的{id}表示处理器需要接受一个由URL组成的参数,且参数名称为id
@PathVariable("id")的意思是获取定义在@RequestMapping中参数名称为id的参数,这样就可以在方法内获取这个参数了
然后通过劫色服务类获取角色对象,并将其绑定到视图中,将视图设置为JSON视图。
//注入角色服务对象 @Autowired RoleService roleService; //{id}代表接收一个参数 @RequestMapping("/getRole/{id}") //注解@PathVariable表示从URL的请求地址中获取参数 public ModelAndView pathVariable(@PathVariable("id") Long id) { Role role = roleService.getRole(id); ModelAndView mv = new ModelAndView(); //绑定数据模型 mv.addObject(role); //设置为JSON视图 mv.setView(new MappingJackson2JsonView()); return mv; }
测试结果:
4.传递JSON参数
假如要传递更多的参数。例如,对于查询参数,假设还有开始行start和限制返回大小的limit,那么加上roleName和note,就有了4个参数。
首先定义分页参数POJO类PageParams类:
package com.ssm.chapter14.pojo; public class PageParams { private int start; private int limit;
/*getter and setter*/ }
然后,在原来的RoleParams类中增加一个PageParams类的对象,即:
package com.ssm.chapter14.pojo; public class RoleParams { private String roleName; private String note; private PageParams pageParams = null;// 分页参数
/*getter and setter*/
}
这样,查询参数和分页参数就都可以被传递了。这时,为了模拟传递过程,在params.jsp中增加JavaScript脚本代码:
/** 传递JSON**/ $(document).ready(function() { //JSON参数和类RoleParams一一对应 var data = { //角色查询参数 roleName : 'role', note : 'note', //分页参数 pageParams : { start : 0, limit : 4 } } //Jquery的post请求 $.post({ url : "./params/findRoles.do", //此处需要告知传递参数类型为JSON,不能缺少 contentType : "application/json", //将JSON转化为字符串传递 data : JSON.stringify(data), //成功后的方法 success : function(result) { } }); });
与之对应的findRoles方法:首先传递的JSON数据需要和对应参数的POJO保持一致。其次,在请求的时候需要告知请求的参数类型为JSON。最后,传递的参数是一个字符串,而不是一个JSON,所以需要将JSON转换成字符串。然后,通过@RequestBody注解,就可以将和JavaScript代码中对应的POJO类对象roleParams传递进去。
@RequestMapping("/findRoles") public ModelAndView findRoles(@RequestBody RoleParams roleParams) { List<Role> roleList = roleService.findRoles(roleParams); ModelAndView mv = new ModelAndView(); //绑定模型 //mv.addObject(roleList); //设置为JSON视图 //mv.setView(new MappingJackson2JsonView()); return mv; }
与之对应的Mapper中的配置:
<select id="findRoles" parameterType="com.ssm.chapter15.pojo.RoleParams" resultType="com.ssm.chapter14.pojo.Role"> select id, role_name as roleName, note from t_role <where> <if test="roleName != null"> and role_name like concat('%', #{roleName}, '%') </if> <if test="note != null"> and note like concat('%', #{note}, '%') </if> </where> limit #{pageParams.start}, #{pageParams.limit} </select>
另外,还需要将原来的params.jsp文件中的action配置删除才能进行正确的测试:
5.接收列表数据和表单序列化
(1)传递数组给控制器,进行一次性删除多个角色的操作
查看删除前的数据库:
mysql> select * from t_role; +----+-------------+--------+ | id | role_name | note | +----+-------------+--------+ | 1 | role_name_1 | note_1 | | 2 | role_name_2 | note_2 | | 3 | role_name_3 | note_3 | | 4 | role_name_4 | note_4 | | 5 | role_name_5 | note_5 | +----+-------------+--------+ 5 rows in set (0.00 sec)
jsp中对应的应该添加的JavaScript代码:
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>参数</title> <!-- 加载Query文件--> <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.js"></script> <script type="text/javascript"> /**传递数组**/ $(document).ready(function() { //删除角色数组 var idList = [ 1, 2, 3 ]; //jQuery的post请求 $.post({ url : "./params/deleteRoles.do", //将JSON转化为字符串传递 data : JSON.stringify(idList), //指定传递数据类型,不可缺少 contentType : "application/json", //成功后的方法 success : function(result) { } }); }); </script> </head> </html>
与之对应的deleteRoles方法:这里的@RequestBody List<Long> idList表示要求Spring MVC 将传递过来的JSON数组数据,转换为对应的Java集合类型。
@RequestMapping("/deleteRoles") public ModelAndView deleteRoles(@RequestBody List<Long> idList) { ModelAndView mv = new ModelAndView(); //删除角色 int total = roleService.deleteRoles(idList); System.out.println(total); //绑定视图 // mv.addObject("total", total); //JSON视图 // mv.setView(new MappingJackson2JsonView()); return mv; }
roleService的deleteRoles方法的实现:
@Override @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public int deleteRoles(List<Long> idList) { int count = 0; for (Long id : idList) { count += roleDao.deleteRole(id); } return count; }
对应映射器Mapper中deleteRoles的配置:
<delete id="deleteRole" parameterType="long"> delete from t_role where id = #{id} </delete>
执行http://localhost:8080/Chapter14/deleteRoles.jsp后,控制台输出:3,并且数据库的结果为:
mysql> select * from t_role; +----+-------------+--------+ | id | role_name | note | +----+-------------+--------+ | 4 | role_name_4 | note_4 | | 5 | role_name_5 | note_5 | +----+-------------+--------+ 2 rows in set (0.00 sec)
(2)新增多个角色
同理addRole.jsp的内容是:
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>参数</title> <!-- 加载Query文件--> <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.js"></script> <script type="text/javascript"> $(document).ready(function () { //新增角色数组 var roleList = [ {roleName: 'role_name_1', note: 'note_1'}, {roleName: 'role_name_2', note: 'note_2'}, {roleName: 'role_name_3', note: 'note_3'} ]; //jQuery的post请求 $.post({ url: "./params/addRoles.do", //将JSON转化为字符串传递 data: JSON.stringify(roleList), contentType: "application/json", //成功后的方法 success: function (result) { } }); }); </script> </head> </html>
与之对应的视图处理器中的addRoles方法:通过@RequestBody注解来获取对应的角色列表参数,这样就可以在控制器中通过@ResponseBody将对应的JSON数据转换成对象列表。
@RequestMapping("/addRoles") public ModelAndView addRoles(@RequestBody List<Role> roleList) { ModelAndView mv = new ModelAndView(); // 新增 int total = roleService.insertRoles(roleList); System.out.println(total); //绑定视图 // mv.addObject("total", total); //JSON视图 // mv.setView(new MappingJackson2JsonView()); return mv; }
roleService的insertRoles方法:
@Override @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public int insertRoles(List<Role> roleList) { int count = 0; for (Role role : roleList) { count += roleDao.insertRole(role); } return count; }
MyBatis映射器Mapper中的insertRoles配置:
<insert id="insertRole" parameterType="com.ssm.chapter14.pojo.Role" keyProperty="id" useGeneratedKeys="true"> insert into t_role (role_name, note) value(#{roleName}, #{note}) </insert>
在浏览器中输入http://localhost:8080/Chapter14/addRoles.jsp然后查看数据库的结果为:
mysql> select * from t_role; +----+-------------+--------+ | id | role_name | note | +----+-------------+--------+ | 4 | role_name_4 | note_4 | | 5 | role_name_5 | note_5 | | 6 | role_name_1 | note_1 | | 7 | role_name_2 | note_2 | | 8 | role_name_3 | note_3 | +----+-------------+--------+ 5 rows in set (0.00 sec)
(3)通过表单序列化也可以将表单数据转换为字符串传递到后台,因为一些隐藏表单需要一定的计算,所以我们也需要在用户点击提交按钮后,通过序列化去提交表单。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>参数</title> <!-- 加载Query文件--> <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.js"> </script> <script type="text/javascript"> $(document).ready(function() { $("#commit").click(function() { var str = $("form").serialize(); //提交表单 $.post({ url : "./params/commonParamPojo2.do", //将form数据序列化,传递给后台,则将数据以roleName=xxx&¬e=xxx传递 data : $("form").serialize(), //成功后的方法 success : function(result) { } }); }); }); </script> </head> <body> <form id="form"> <table> <tr> <td>角色名称</td> <td><input id="roleName" name="roleName" value="" /></td> </tr> <tr> <td>备注</td> <td><input id="note" name="note" /></td> </tr> <tr> <td></td> <td align="right"><input id="commit" type="button" value="提交" /></td> </tr> </table> </form> </body> </html>
对应的commonParamPojo2方法:从表单获取数据后,点击提交按钮,就会把数据在后台打印出来。
@RequestMapping("/commonParamPojo2") public ModelAndView commonParamPojo2(String roleName, String note) { System.out.println("roleName =>" + roleName); System.out.println("note =>" + note); ModelAndView mv = new ModelAndView(); // mv.setViewName("index"); return mv; }
这里需要说明的是,jquery中的$.post方法中,在执行完方法后,是无法跳转到Spring MVC 返回的ModelAndView类型的mv页面的。具体可以了解Ajax的内容。
二、重定向
通过之前的例子,我们知道,可以showRoleJsonInfo方法可以接收三个参数,然后就可以将这些参数转化为视图,通过JSON的形式展示在页面上。
@RequestMapping("/showRoleJsonInfo") public ModelAndView showRoleJsonInfo(Long id, String roleName, String note) { ModelAndView mv = new ModelAndView(); mv.setView(new MappingJackson2JsonView()); mv.addObject("id", id); mv.addObject("roleName", roleName); mv.addObject("note", note); return mv; }
例如:浏览器中输入参数,就可以将参数信息转化成视图,然后展示出来。
但是,如果想要实现:每当新增一一个角色信息时,需要其将数据以JSON视图的形式展示给请求者。
实现方法是:在数据保存到数据库后,由数据库返回角色编号,再将角色信息传递showRoleJsonInfo方法,就可以展示JSON视图给请求者了。
1.在视图控制器中新增重定向功能的方法:这里需要注意的是,在执行完roleService.insertRole(role);语句后,插入数据库的id会回填到role对象中。
然后通过返回字符串类型的"redirect:./showRoleJsonInfo.do"来进行重定向,其中如果字符串中带有“redirect”,那么就会认为是一个重定向。
@RequestMapping("/addRole") //Model为重定向数据模型,Spring MVC会自动初始化它 public String addRole(Model model, String roleName, String note) { Role role = new Role(); role.setRoleName(roleName); role.setNote(note); //插入角色后,会回填角色编号 roleService.insertRole(role); //绑定重定向数据模型 model.addAttribute("roleName", roleName); model.addAttribute("note", note); model.addAttribute("id", role.getId()); return "redirect:./showRoleJsonInfo.do"; }
只需要指定roleName和note参数即可,
2.不仅可以通过字符串来实现重定向,还可以通过返回视图来实现重定向
@RequestMapping("/addRole2") //ModelAndView对象Spring MVC会自定初始化它 public ModelAndView addRole2(ModelAndView mv, String roleName, String note) { Role role = new Role(); role.setRoleName(roleName); role.setNote(note); //插入角色后,会回填角色编号 roleService.insertRole(role); //绑定重定向数据模型 mv.addObject("roleName", roleName); mv.addObject("note", note); mv.addObject("id", role.getId()); mv.setViewName("redirect:./showRoleJsonInfo.do"); return mv; }
3.上面的例子都是传递简单的String类型的参数,有些时候需要传递角色POJO,而不是一个个字段的传递。
修改showRoleJsonInfo方法成showRoleJsonInfo2,可以以JSON的形式展示role对象
@RequestMapping("/showRoleJsonInfo2") public ModelAndView showRoleJsonInfo(Role role) { ModelAndView mv = new ModelAndView(); mv.setView(new MappingJackson2JsonView()); mv.addObject("role", role); return mv; }
但是,在URL重定向的过程中,并不能有效传递对象,因为HTTP的重定向参数是以字符串传递的。为了解决这个问题,可以使用Spring MVC 的flash属性,即RedirectAttributes参数,
@RequestMapping("/addRole3") //RedirectAttribute对象Spring MVC会自动初始化它 public String addRole3(RedirectAttributes ra, Role role) { //插入角色后,会回填角色编号 roleService.insertRole(role); //绑定重定向数据模型 ra.addFlashAttribute("role", role); return "redirect:./showRoleJsonInfo2.do"; }
这样就能传递POJO对象到下一个地址了,Spring MVC 的实现方式是:将数据保存在Session中,重定向后就会将其消除,这样就能传递数据给下一个地址了。
三、保存并获取属性参数(request、session、cookie和HTTP header)
Spring MVC 可以通过一些注解从HTTP的request对象或者Session对象中获取数据。
1.@RequestAttribute注解可以从HTTP的request对象中取出请求属性,只是范围是在一次请求中存在。
request.jsp的内容如下,
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% //设置请求属性 request.setAttribute("id", 11L); //转发给控制器 request.getRequestDispatcher("./attribute/requestAttribute.do").forward(request, response); %> </body> </html>
对应的控制器中的sessionAttribute方法:jsp文件中设置request的属性id为1,然后进行了转发控制器,这样将有对应的控制器去处理业务逻辑,然后由AttributeController控制器去处理它,通过(@RequestAttribute("id") Long id)将jsp中设置的request属性获取到。
@RequestAttribute和@RequestParam注解一样,默认是不能为空的,否则系统会抛出异常。但是,它们都有一个required配置项,只要配置成false,参数就可以为空了。
package com.ssm.chapter14.controller; @Controller @RequestMapping("/attribute") public class AttributeController { @Autowired private RoleService roleService = null; @RequestMapping("/requestAttribute") public ModelAndView reqAttr(@RequestAttribute("id") Long id) { ModelAndView mv = new ModelAndView(); Role role = roleService.getRole(id); mv.addObject("role", role); mv.setView(new MappingJackson2JsonView()); return mv; }
}
在浏览器中输入http://localhost:8080/Chapter14/request.jsp,就可以获取jsp中定义的request属性值,并跳转到指定的界面。
2.@SessionAttribute和@SessionAttributes
这两个注解都和HTTP的会话对象有关,在浏览器和服务器保持联系的时候HTTP会创建一个会话对象,这样可以让我们在和服务器会话期间通过它读/写会话对象的属性,缓存一定的数据信息。
(1)通过@SessionAttributes设置会话属性
@SessionAttributes注解只能对类进行标注,不能对方法或者参数注解。它可以配置属性名称或者属性类型。它的作用是当这个类被注解后,Spring MVC 执行完控制器的逻辑后,将数据模型中对应的属性名称或者属性类型保存到HTTP的Session对象中。
sessionAttrs方法中,通过首先根据传递进来的id,通过查询得到role对象,而由于AttributeController类中通过@SessionAttributes设置了名称和类型,因此,id和role对象都会保存到Session对象中。
(2)通过@SessionAttribute获取会话属性
当浏览器中输入/attribute/sessionAttributes.do?id=1时,id和role就会被保存到Session对象中,可以在sessionAttribute.jsp中通过session.getAttribute("role")和session.getAttribute("id")两个方法获取到之前保存进去的role和id。
package com.ssm.chapter14.controller; @Controller @RequestMapping("/attribute") // 可以配置数据模型的名称和类型,两者取或关系 @SessionAttributes(names ={"id"}, types = { Role.class }) public class AttributeController { @Autowired private RoleService roleService = null; @RequestMapping("/sessionAttributes") public ModelAndView sessionAttrs(Long id) { ModelAndView mv = new ModelAndView(); Role role = roleService.getRole(id); //根据类型,session将会保存角色信息 mv.addObject("role", role); //根据名称,session将会保存id mv.addObject("id", id); //视图名称,定义跳转到一个JSP文件上 mv.setViewName("sessionAttribute"); return mv; } @RequestMapping("/sessionAttribute") public ModelAndView sessionAttr(@SessionAttribute("id") Long id) { ModelAndView mv = new ModelAndView(); Role role = roleService.getRole(id); mv.addObject("role", role); mv.setView(new MappingJackson2JsonView()); return mv; }
}
和之前的设置request属性值的jsp脚本类似:编写设置Session属性的jsp脚本,然后就会跳转到sessionAttribute控制器去处理。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>session</title> </head> <body> <% //设置Session属性 session.setAttribute("id", 7L); //执行跳转 response.sendRedirect("./attribute/sessionAttribute.do"); %> </body> </html>
3.@CookieValue和@RequestHeader
可以通过@CookieValue和@RequestHeader注解分别从Cookie和HTTP Header中读取信息。
@RequestMapping("/getHeaderAndCookie") public String testHeaderAndCookie( @RequestHeader(value="User-Agent", required = false, defaultValue = "attribute") String userAgent, @CookieValue(value = "JSESSIONID", required = true, defaultValue = "MyJsessionId") String jsessionId) { System.out.println("User-Agent:" + userAgent); System.out.println("JSESSIONID:" + jsessionId); return "index"; }
四、拦截器
拦截器是Spring MVC 中强大的组件,它可以在进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作。
回顾Spring MVC 执行流程:Spring MVC 会在启动期间就通过@RequestMapping的注解解析URL和处理器的对应关系,在运行的时候通过请求找到对应的HandlerMapping,然后构建一个执行的责任链对象即HandlerExecutionChain对象,而HandlerExecutionChain对象中包含了handler对象,这个对象指向了控制器所对应的方法和拦截器。
1.定义拦截器
Spring 要求处理器的拦截器都要实现接口org.springframework.web.servlet.HandlerInterceptor,这个接口定义了3个方法:
- preHandle方法:在处理器之前执行的前置方法,这样 Spring MVC 可以在进入处理器前处理一些方法。它将返回一个boolean值,会影响到后面 Spring MVC 的流程。
- postHandle方法:在处理器之后执行的后置方法,处理器的逻辑完成后运行它。
- afterCompletion方法:无论是否产生异常都会在渲染视图后执行的方法。
2.拦截器的执行流程:
需要注意的是,当前置方法返回false时,就不会再执行后面的逻辑了。
3.开发拦截器
Spring MVC 中拦截器的设计:
其中,Spring MVC 还提供了公共拦截器HandlerInterceptorAdapter,当只想实现3个拦截器方法中的一到两个时,可以继承这个公共拦截器,然后按照需要重写需要的方法就可以了。
创建角色拦截器RoleInterceptor类,其继承了HandlerInterceptorAdapter公共拦截器类:
package com.ssm.chapter15.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; public class RoleInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.err.println("preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.err.println("postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.err.println("afterCompletion"); } }
然后还需要在Spring MVC 的配置文件dispatcher-servlet.xml中进行配置:需要声明RoleInterceptor所在的包和类的全限定名
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/role/*.do" /> <bean class="com.ssm.chapter14.interceptor.RoleInterceptor" /> </mvc:interceptor> </mvc:interceptors>
4.多个拦截器执行的顺序
假设现在有3个拦截器,在各自的方法中,分别打印拦截器方法名+编号(1,2,3)
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/role/*.do" /> <bean class="com.ssm.chapter14.interceptor.RoleInterceptor1" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/role/*.do" /> <bean class="com.ssm.chapter14.interceptor.RoleInterceptor2" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/role/*.do" /> <bean class="com.ssm.chapter14.interceptor.RoleInterceptor3" /> </mvc:interceptor> </mvc:interceptors>
(1)加入三个拦截器的preHandle方法的返回值都是true,那么会先从第一个拦截器开始进入前置方法,前置方法是顺序执行的,而后置和完成方法则是逆序运行的,这里参考拦截器执行流程。
preHandle1
preHandle2
preHandle3
业务逻辑
postHandle3
postHandle2
postHandle1
afterCompletion3
afterCompletion2
afterCompletion1
(2)加入第二个拦截器的preHandle方法的返回值为false,那么后面的拦截器的preHandle方法都不会运行了,即后面的所有拦截器都不起作用。
preHandle1
preHandle2
afterCompletion1
五、验证表单
在实际工作中,得到数据后的第一步就是检验数据的正确性,如果存在录入上的问题,一般会通过注解校验,发现错误后返回给用户,但是对于一些逻辑上的错误,比如购买金额=购满数量×单价,这样的规则就很难使用注解方式进行验证了,这个时候可以使用验证器(Validator)规则去验证。
1.使用JSR 303注解验证输入内容
Spring提供了对Bean的功能校验,通过注解表明哪个Bean需要进行验证以及验证内容:
首先,新建一个POJO类,并且根据实际需要在字段上分别进行标注:
package com.ssm.chapter14.pojo;public class Transaction { // 产品编号 @NotNull // 不能为空 private Long productId; // 用户编号 @NotNull // 不能为空 private Long userId; // 交易日期 @Future // 只能是将来的日期 @DateTimeFormat(pattern = "yyyy-MM-dd") // 日期格式化转换 @NotNull // 不能为空 private Date date; // 价格 @NotNull // 不能为空 @DecimalMin(value = "0.1") // 最小值0.1元 private Double price; // 数量 @Min(1) // 最小值为1 @Max(100) // 最大值 @NotNull // 不能为空 private Integer quantity; // 交易金额 @NotNull // 不能为空 @DecimalMax("500000.00") // 最大金额为5万元 @DecimalMin("1.00") // 最小交易金额1元 private Double amount; // 邮件 @Pattern(// 正则式 regexp = "^([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)*@" + "([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[\.][A-Za-z]{2,3}([\.][A-Za-z]{2})?$", // 自定义消息提示 message = "不符合邮件格式") private String email; // 备注 @Size(min = 0, max = 256) // 0到255个字符 private String note;
/**************************getter and setter*****************************************/ }
然后创建一个表单,其中action指定了提交过后应该调用的控制器视图:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>validate</title>
</head>
<body>
<form action = "./validate/annotation.do">
<table>
<tr>
<td>产品编号:</td>
<td><input name="productId" id="productId"/></td>
</tr>
...
<tr><td colspan="2" align="right"> <input type="submit" value="提交"/> </tr>
</table>
<form>
</body>
</html>
然后定义一个当Bean的检验失败后的处理器,@Valid Transaction trans中使用@Valid注解表明这个Bean将会被检验,而另外一个Errors的参数则是用于保存是否存在错误信息的。
package com.ssm.chapter14.controller; import @Controller @RequestMapping("/validate") public class ValidateController { @RequestMapping("/annotation") public ModelAndView annotationValidate(@Valid Transaction trans, Errors errors) { // 是否存在错误 if (errors.hasErrors()) { // 获取错误信息 List<FieldError> errorList = errors.getFieldErrors(); for (FieldError error : errorList) { // 打印字段错误信息 System.err.println("fied :" + error.getField() + " " + "msg:" + error.getDefaultMessage()); } } ModelAndView mv = new ModelAndView(); mv.setViewName("index"); return mv; } }
测试结果:
打印结果:
fied :quantity msg:最大不能超过100
fied :productId msg:不能为null
fied :date msg:需要是一个将来的时间
fied :userId msg:不能为null
fied :email msg:不符合邮件格式
fied :price msg:必须大于或等于0.1
2.使用验证器Validator规则验证输入内容
有时候除了简单的输入格式、非空型等校验,也需要一定的业务检验,Spring 提供了Validator接口来实现校验,它将在进入控制器逻辑之前对参数的合法性进行检验。
首先定义验证器,必须实现Validator接口:首先验证是否是Transaction对象,如果是,就验证交易金额是否等于单价×数量
package com.ssm.chapter14.validator; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import com.ssm.chapter14.pojo.Transaction; public class TransactionValidator implements Validator { @Override public boolean supports(Class<?> clazz) { //判断验证是否为Transaction,如果是则进行验证 return Transaction.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { Transaction trans = (Transaction) target; //求交易金额和价格×数量的差额 double dis = trans.getAmount() - (trans.getPrice() * trans.getQuantity()); //如果差额大于0.01,则认为业务错误 if (Math.abs(dis) > 0.01) { //加入错误信息 errors.rejectValue("amount", null, "交易金额和购买数量与价格不匹配"); } } }
然后,还需要将验证器TransactionValidator和控制器绑定起来,Spring MVC 提供了@InitBinder注解,通过这个注解就可以将验证器和控制器绑定到一起了:
package com.ssm.chapter14.controller; import @Controller @RequestMapping("/validate") public class ValidateController { @InitBinder public void initBinder(DataBinder binder) { // 数据绑定器加入验证器 binder.setValidator(new TransactionValidator()); } @RequestMapping("/validator") public ModelAndView validator(@Valid Transaction trans, Errors errors) { // 是否存在错误 if (errors.hasErrors()) { // 获取错误信息 List<FieldError> errorList = errors.getFieldErrors(); for (FieldError error : errorList) { // 打印字段错误信息 System.err.println("fied :" + error.getField() + " " + "msg:" + error.getDefaultMessage()); } } ModelAndView mv = new ModelAndView(); mv.setViewName("index"); return mv; } }
最后,还需要修改表单的action项为新的当前的控制器<form action = "./validate/validator.do">,然后进行测试:在控制台打印出:
fied :amount msg:交易金额和购买数量与价格不匹配
还需要注意的是,JSR 303注解方式和验证器方式不能同时使用,不过可以在使用JSR 303注解额方式得到基本的检验信息后,再使用自己的方法进行验证。
六、数据模型
视图是业务处理后展现给用户的内容,不过一般伴随着业务处理返回的数据,用来给用户查看。Spring MVC 的流程是从控制器获取数据后,会装载数据到数据模型和视图中,然后将视图名称转发到视图解析器中,通过解析器解析后得到最终视图,最后将数据模型渲染到视图中,展示最终的结果给用户。
之前一直使用ModelAndView来定义视图类型,包括JSON视图,也用它来加载数据模型。ModelAndView有一个类型为ModelMap的属性model,而ModelMap继承了LinkedHashMap<String, Object>,因此它可以存放各种键值对,为了进一步定义数据模型功能,Spring 还创建了类ExtendedModelMap,这个类实现了数据模型定义的Model 接口,并且还在此基础上派生了关于数据绑定的类---BindAwareModelMap:
在控制器的方法中,可以把ModelAndView、Model、ModelMap作为参数。在Spring MVC 运行的时候,会自动初始化它们,因此可以选择 ModelMap 或者 Model 作为数据模型。
@RequestMapping(value = "/getRoleByModelMap", method = RequestMethod.GET) public ModelAndView getRoleByModelMap(@RequestParam("id") Long id, ModelMap modelMap) { Role role = roleService.getRole(id); ModelAndView mv = new ModelAndView(); mv.setViewName("roleDetails"); modelMap.addAttribute("role", role); return mv; } @RequestMapping(value = "/getRoleByModel", method = RequestMethod.GET) public ModelAndView getRoleByModel(@RequestParam("id") Long id, Model model) { Role role = roleService.getRole(id); ModelAndView mv = new ModelAndView(); mv.setViewName("roleDetails"); model.addAttribute("role", role); return mv; } @RequestMapping(value = "/getRoleByMv", method = RequestMethod.GET) public ModelAndView getRoleByMv(@RequestParam("id") Long id, ModelAndView mv) { Role role = roleService.getRole(id); mv.setViewName("roleDetails"); mv.addObject("role", role); return mv; }
在浏览器直接输入http://localhost:8080/Chapter14/role/getRoleByModel.do?id=8也会得到正确的跳转视图。
事实上,无论是 Model 还是 ModelMap,Spring MVC 创建的是一个BindingAwareModelMap 实例,而 BindingAwareModelMap 是一个继承了 ModelMap 实现了 Model 接口的类,所以就有了相互转换的功能。
七、视图和视图解析器
视图是展示给用户的内容,而在此之前,要通过控制器得到对应的数据模型,如果是非逻辑视图,则不会经过视图解析器定位视图,而是直接将数据模型渲染便结束了;而逻辑视图则是要对其进一步解析,以定位真实视图,这就是视图解析器的作用了。而视图则是把从控制器查询回来的数据模型进行渲染,以展示给请求者查看。
1.视图
在请求之后,Spring MVC 控制器获取了对应的数据,绑定到数据模型中,那么视图就可以展示数据模型的信息了。
视图又分为逻辑视图和非逻辑视图,比如MappingJackson2JsonView是一个非逻辑视图,它的目的就是将数据模型转换为一个JSON视图,展现给用户,无须对视图名字再进行下一步的解析。这其中,由于非逻辑视图在没有视图解析器的情况下就可以进行渲染,最终将其绑定的数据模型转换为JSON数据。
Spring MVC 中定义了多种视图,它们都需要实现视图接口--View,而View接口中主要有方法getContentType和render,其中getContentType方法表示返回一个字符串,表明给用户什么类型的文件响应,可以使HTML、JSON、PDF等,而render方法则是一个渲染视图的方法,其参数包括其数据模型Model,HTTP请求对象和HTTP响应对象。当控制器返回ModelAndView 的时候,视图解析器就会解析它,然后将数据模型传递给 render 方法,这样就能将数据模型渲染成各种视图,然后通过HTTP请求兑现和HTTP响应对象展示给用户了。
public interface View { ... String getContentType(); void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throw Exception; }
而InternalResourceView是一个逻辑视图,对于逻辑视图而言它需要一个视图解析器,视图解析器的作用也就是,通过前缀和后缀加上视图名称就能够找到对应的JSP文件,然后把数据模型渲染到JSP文件中,这样便能展现视图给用户了。
<!-- 定义视图解析器 --> <!-- 找到Web工程/WEB-INF/JSP文件夹,且文件结尾为jsp的文件作为映射 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
2.视图解析器
对于逻辑视图而言,把视图名称转换为逻辑视图是一个必备的过程,InternalResourceView会加载到 Spring MVC 的视图解析器列表中去,当返回ModelAndView的时候,Spring MVC 就会在视图解析器列表中遍历,找到对应的视图解析器去解析视图。
视图解析器的定义如下,其中viewName表示传递进来的视图名称,而Locale类型的locale是国际化对象。
public interface ViewResolver { View resolveViewName(String viewName, Local locale) throws Exception; }
有时候在控制器中并没有返回一个 ModelAndView, 而是只返回了一个字符串,它也能够渲染视图,因为视图解析器生成了对应的视图:
@RequestMapping(value = "/index", method = RequestMethod.GET) public String index(@RequestParam("id") Long id, ModelMap model) { Role role = roleService.getRole(id); model.addAttribute("role", role); return "roleDetails"; }
3.实例:Excel视图的使用
需求:用户通过输入URL,然后通过GET请求下载到保存了数据库中所有的查询记录的Excel表
(1)选择视图类
对于Excel而言,Spring MVC 所推荐的是使用AbstractXlsView,根据视图类的关系可以看到,AbstractXlsView继承了AbstractView类,而AbstractView类又实现了View接口。
由于AbstractXlsView是抽象类,因此需要实现其中的抽象方法buildExcelDocument:
protected abstract void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception);
其中,model表示数据模型,workbook表示POI workbook对象,这个方法的主要任务是创建一个Workbook,它要用到POI的API。
(2)自定义导出接口
目前的需求是导出所有就是信息,但是将来或许还需要增加其他的导出功能,因此,先定义一个接口,这个接口的功能是让开发者自定义生成Excel的规则。
package com.ssm.chapter14.view; import java.util.Map; import org.apache.poi.ss.usermodel.Workbook; public interface ExcelExportService { /*** * 生成exel文件规则 * @param model 数据模型 * @param workbook excel workbook */ public void makeWorkBook(Map<String, Object> model, Workbook workbook); }
(3)定义Excel视图
即Excel视图类:对于导出来说,还需要一个下载文件名称,所以还需要定义一个fileName属性。由于该视图不是一个逻辑视图,所以无需视图解析器也可以运行它。
对于buildExcelDocument方法来说,其最后一行才是关键:excelExpService.makeWorkBook(model, workbook);,意思就是调用ExcelExportService接口的makeWorkBook方法使用自定义的规则进行Excel创建。即可以根据需要进行自定义生成Excel的规则。
package com.ssm.chapter14.view;
import
public class ExcelView extends AbstractXlsView { // 文件名 private String fileName = null; // 导出视图自定义接口 private ExcelExportService excelExpService = null; // 构造方法1 public ExcelView(ExcelExportService excelExpService) { this.excelExpService = excelExpService; } // 构造方法2 public ExcelView(String viewName, ExcelExportService excelExpService) { this.setBeanName(viewName); }
/******** getter and setter **********/
@Override protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { // 没有自定义接口 if (excelExpService == null) { throw new RuntimeException("导出服务接口不能为null!!"); } // 文件名不为空,为空则使用请求路径中的字符串作为文件名 if (!StringUtils.isEmpty(fileName)) { // 进行字符转换 String reqCharset = request.getCharacterEncoding(); reqCharset = reqCharset == null ? "UTF-8" : reqCharset; fileName = new String(fileName.getBytes(reqCharset), "ISO8859-1"); // 设置下面文件名 response.setHeader("Content-disposition", "attachment;filename=" + fileName); } // 回调接口方法,使用自定义生成Excel文档 excelExpService.makeWorkBook(model, workbook); } }
(4)定义控制器
其中ExcelView ev = new ExcelView(exportService());将得到ExcelView类型的视图ev,然后mv.addObject("roleList", roleList);加入数据模型,最后mv.setView(ev);将视图设置为ev。
ExcelView ev = new ExcelView(exportService());中exportService()方法就是(5)中定义的ExcelExportService接口的实现类的方法。
@RequestMapping(value = "/export", method = RequestMethod.GET) public ModelAndView export() { //模型和视图 ModelAndView mv = new ModelAndView(); //Excel视图,并设置自定义导出接口 ExcelView ev = new ExcelView(exportService()); //文件名 ev.setFileName("所有角色.xlsx"); //设置SQL后台参数 RoleParams roleParams = new RoleParams(); //限制1万条 PageParams page = new PageParams(); page.setStart(0); page.setLimit(10000); roleParams.setPageParams(page); //查询 List<Role> roleList = roleService.findRoles(roleParams); //加入数据模型 mv.addObject("roleList", roleList); mv.setView(ev); return mv; }
(5)定义ExcelExportService接口的实现:这里是使用了Lambda表达式实现的,看起来比较高级。
@SuppressWarnings({ "unchecked"}) private ExcelExportService exportService() { //使用Lambda表达式自定义导出excel规则 return (Map<String, Object> model, Workbook workbook) -> { //获取用户列表 List<Role> roleList = (List<Role>) model.get("roleList"); //生成Sheet Sheet sheet= workbook.createSheet("所有角色"); //加载标题 Row title = sheet.createRow(0); title.createCell(0).setCellValue("编号"); title.createCell(1).setCellValue("名称"); title.createCell(2).setCellValue("备注"); //便利角色列表,生成一行行的数据 for (int i=0; i<roleList.size(); i++) { Role role = roleList.get(i); int rowIdx = i + 1; Row row = sheet.createRow(rowIdx); row.createCell(0).setCellValue(role.getId()); row.createCell(1).setCellValue(role.getRoleName()); row.createCell(2).setCellValue(role.getNote()); } }; }
(6)测试:在浏览器中输入:http://localhost:8080/Chapter14/role/export.do就可以下载到一个名为“所有角色.xlsx”的Excel文件,打开该文件,可以看到正确显示了数据库中的记录:
八、上传文件
Spring MVC 为上传文件提供了良好的支持。首先 Spring MVC 的文件上传是通过MultipartResolver处理的,对于MultipartResolver而言它只是一个接口,它有两个实现类:CommonMultipartResovler和StandardMultipartResolver。其中,StandardMultipartResolver不需要引入任何第三方包即可实现。无论使用哪个类,都需要配置一个MultipartResolver
1.MultipartResolver配置
(1)通过注解配置StandardMultipartResolver
对于StandardMultipartResolver来说,其构造方法没有参数,因此很容易对其进行初始化。
@Bean(name = "multipartResolver") public MultipartResolver initMultipartResolver() { return new StandardServletMultipartResolver(); }
但是,仅仅这样配置是不够的,还需要对上传文件进行配置,例如限制单个文件的大小,设置上传路径等,为了进行设置,可以在Spring MVC 初始化的时候对MultipartResolver进行配置:
如果需要通过Java配置 Spring MVC 的初始化,只需要继承AbstractAnnotationConfigDispatcherServletInitializer 类就可以了,通过继承它就可以进行注解配置了,这个类中可以覆盖customizeRegistration方法,它是一个用于初始化DispatcherServlet设置的方法。
package com.ssm.chapter15.config; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletRegistration.Dynamic; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { //Spring IoC容器配置 @Override protected Class<?>[] getRootConfigClasses() { //可以返回Spring的Java配置文件数组 return new Class<?>[] {}; } //DispatcherServlet的URI映射关系配置 @Override protected Class<?>[] getServletConfigClasses() { //可以返回Spring的Java配置文件数组 return new Class<?>[] { WebConfig.class }; } //DispatchServlet[修改为:DispatcherServlet]拦截请求匹配 @Override protected String[] getServletMappings() { return new String[] { "*.do" }; } /** * @param dynamic Servlet动态加载配置 */ @Override protected void customizeRegistration(Dynamic dynamic) { //文件上传路径 String filepath = "d:/mvc/upload"; //5MB Long singleMax = (long) (5*Math.pow(2, 20)); //10MB Long totalMax = (long) (10*Math.pow(2, 20)); //配置MultipartResolver,限制请求,单个文件5MB,总共文件10MB dynamic.setMultipartConfig(new MultipartConfigElement(filepath, singleMax, totalMax, 0)); } }
其中,customizeRegistration方法中设置了MultipartResolver的属性,包括文件路径、单个文件大小、全部文件大小。
(2)通过XML配置StandardMultipartResolver
还可以通过在Web.xml中实现对MultipartResolver的初始化,然后通过XML或者注解生成一个AbstractAnnotationConfigDispatcherServletInitializer即可。
<!--MultipartResolver参数 --> <multipart-config> <location>e:/mvc/uploads/</location> <max-file-size>5242880</max-file-size> <max-request-size>10485760</max-request-size> <file-size-threshold>0</file-size-threshold> </multipart-config>
(3)通过注解配置CommonMultipartResovler,但是这种方式需要依赖于第三方的包
@Bean(name = "multipartResolver") public MultipartResolver initCommonsMultipartResolver() { //文件上传路径 String filepath = "d:/mvc/uploads"; //5MB Long singleMax = (long) (5 * Math.pow(2, 20)); //10MB Long totalMax = (long) (10 * Math.pow(2, 20)); CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setMaxUploadSizePerFile(singleMax); multipartResolver.setMaxUploadSize(totalMax); try { multipartResolver.setUploadTempDir(new FileSystemResource(filepath)); } catch (IOException e) { e.printStackTrace(); } return multipartResolver; }
2.提交上传文件表单
需要将enctype="multipart/form-data设置成这样,否则 Spring MVC 会解析失败。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>文件上传</title> </head> <body> <form method="post" action="./file/upload.do" enctype="multipart/form-data" > <input type="file" name="file" value="请选择上传的文件"/> <input type="submit" value="提交"/> </form> </body> </html>
3.与之对应,需要有一个upload控制器方法
package com.ssm.chapter15.controller; import @Controller @RequestMapping("/file") public class FileController implements ApplicationContextAware { @RequestMapping("/upload") public ModelAndView upload(HttpServletRequest request) { // 进行转换 MultipartHttpServletRequest mhsr = (MultipartHttpServletRequest) request; // 获得请求上传的文件 MultipartFile file = mhsr.getFile("file"); // 设置视图为JSON视图 ModelAndView mv = new ModelAndView(); mv.setView(new MappingJackson2JsonView()); // 获取原始文件名 String fileName = file.getOriginalFilename(); // 目标文件 File dest = new File(fileName); try { // 保存文件 file.transferTo(dest); // 保存成功 mv.addObject("success", true); mv.addObject("msg", "上传文件成功"); } catch (IllegalStateException | IOException e) { // 保存失败 mv.addObject("success", false); mv.addObject("msg", "上传文件失败"); e.printStackTrace(); } return mv; }
}
4.测试
项目目录为:
其中,采用1(1)中的配置,然而首先需要创建目录,d:/mvc/upload,然后在浏览器中输入:http://localhost:8080/Chapter15.2/file.jsp
选择文件后,点击提交,跳转到下面的页面,同时,d:/mvc/upload下也发现了之前选择的文件:
5.问题分析
这里会有一个问题,就是控制器中的upload方法的参数是HttpServletRequest request,这样会造成API侵入,即调用了Servlet的API
@RequestMapping("/upload") public ModelAndView upload(HttpServletRequest request) {....}
解决方法是,将参数修改成MultipartFile或者Part类对象,这样做的好处是把代码从Servlet API 中解放出来,这体现了Spring 的思维,即高度的解耦合性。
下面两个方法都通过了测试。
// 使用MultipartFile @RequestMapping("/uploadMultipartFile") public ModelAndView uploadMultipartFile(MultipartFile file) { // 定义JSON视图 ModelAndView mv = new ModelAndView(); mv.setView(new MappingJackson2JsonView()); // 获取原始文件名 String fileName = file.getOriginalFilename(); file.getContentType(); // 目标文件 File dest = new File(fileName); try { // 保存文件 file.transferTo(dest); mv.addObject("success", true); mv.addObject("msg", "上传文件成功"); } catch (IllegalStateException | IOException e) { mv.addObject("success", false); mv.addObject("msg", "上传文件失败"); e.printStackTrace(); } return mv; } // 使用Part @RequestMapping(value="/uploadPart", method=RequestMethod.POST) public ModelAndView uploadPart(Part file) { ModelAndView mv = new ModelAndView(); mv.setView(new MappingJackson2JsonView()); // 获取原始文件名 String fileName = file.getSubmittedFileName(); try { // 保存文件 file.write("d:/mvc/upload/" + fileName); mv.addObject("success", true); mv.addObject("msg", "上传文件成功"); } catch (IllegalStateException | IOException e) { mv.addObject("success", false); mv.addObject("msg", "上传文件失败"); e.printStackTrace(); } return mv; }