Spring MVC(2)Spring MVC 组件开发

时间:2021-05-16 06:48:17

  一、控制器接收各类请求参数

  代码测试环境:

  接收各类参数的控制器--ParamsController

package com.ssm.chapter15.controller;

@Controller
@RequestMapping("/params")
public class ParamsController {
// 各种控制器方法
}

  先看一下目录结构:

  Spring MVC(2)Spring MVC 组件开发

  这里需要知道的知识点是,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中提交的参数。

  测试:首先输入访问表单,输入任意参数,并提交

  Spring MVC(2)Spring MVC 组件开发

  然后,正确跳转到./params/commonParams.do?roleName=mingcheng&note=beizhu,这一URL,说明参数传递成功。

  Spring MVC(2)Spring MVC 组件开发

  但是,在参数很多的情况下,再使用这样的方式,显然所写方法的参数就会非常多,这是应该考虑到使用一个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,然后也可以得到正确的测试结果:

  Spring MVC(2)Spring MVC 组件开发

  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;
}

  测试结果:

  Spring MVC(2)Spring MVC 组件开发

  

  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&&note=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;
}

  例如:浏览器中输入参数,就可以将参数信息转化成视图,然后展示出来。

  Spring MVC(2)Spring MVC 组件开发

  但是,如果想要实现:每当新增一一个角色信息时,需要其将数据以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参数即可,

  Spring MVC(2)Spring MVC 组件开发

  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中,重定向后就会将其消除,这样就能传递数据给下一个地址了。

  Spring MVC(2)Spring MVC 组件开发

  三、保存并获取属性参数(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属性值,并跳转到指定的界面。

  Spring MVC(2)Spring MVC 组件开发

  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.拦截器的执行流程:

  Spring MVC(2)Spring MVC 组件开发

  需要注意的是,当前置方法返回false时,就不会再执行后面的逻辑了。

  3.开发拦截器

  Spring MVC 中拦截器的设计:

  Spring MVC(2)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需要进行验证以及验证内容:

  Spring MVC(2)Spring MVC 组件开发

  首先,新建一个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() + "\t" + "msg:" + error.getDefaultMessage());
}
}
ModelAndView mv = new ModelAndView();
mv.setViewName("index");
return mv;
}
}

  测试结果:

  Spring MVC(2)Spring MVC 组件开发

  打印结果:

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() + "\t" + "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:

  Spring MVC(2)Spring MVC 组件开发

  在控制器的方法中,可以把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(2)Spring MVC 组件开发

  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 就会在视图解析器列表中遍历,找到对应的视图解析器去解析视图。

  Spring MVC(2)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(2)Spring MVC 组件开发

  八、上传文件

  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.测试

  项目目录为:

  Spring MVC(2)Spring MVC 组件开发

  其中,采用1(1)中的配置,然而首先需要创建目录,d:/mvc/upload,然后在浏览器中输入:http://localhost:8080/Chapter15.2/file.jsp

  选择文件后,点击提交,跳转到下面的页面,同时,d:/mvc/upload下也发现了之前选择的文件:

  Spring MVC(2)Spring MVC 组件开发

  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;
}