使用SpringMVC实现REST风格的CRUD功能

  这篇文章介绍如何使用SpringMVC实现简单的、REST风格的员工信息的增删改查操作。项目源码附在我的github中。   

需求

① 查看所有员工信息:(url:emps,method:GET)

image_1b1dlbuog1jhq1u2dfi010uf153v9.png-49.9kB

② 添加员工信息,添加后重定向到显示员工信息页面:(url:emp,method:POST)

image_1b1dlh5bik4316kl11kq75jqvi13.png-16.4kB

image_1b1dlfl0q1i2l18751ts2m4c1nldm.png-16.1kB

③ 更新员工信息。LastName字段不可修改,要求能够回显表单,更新后重定向到显示员工信息页面:(url:emp/{id},method:PUT)

④ 删除员工信息,删除后重定向到显示员工信息页面。(url:emp/{id},method:DELETE)

实现

Employee.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Employee {
private Integer id;
@NotEmpty
private String lastName;
@Email
private String email;
// 1 male, 0 female
private Integer gender;
private Department department;
//方法略
}

Department.java:

1
2
3
4
5
6
7
public class Department {
private Integer id;
private String departmentName;
//方法略
}

EmployeeDao.java: (其中关于数据库的操作仅采用模拟方式实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartmentDao departmentDao;
static{
employees = new HashMap<Integer, Employee>();
try {
employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1, new Department(101, "D-AA"),new SimpleDateFormat("yyyy-MM-dd").parse("1994-03-21")));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1, new Department(102, "D-BB"),new SimpleDateFormat("yyyy-MM-dd").parse("1994-03-21")));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0, new Department(103, "D-CC"),new SimpleDateFormat("yyyy-MM-dd").parse("1994-03-21")));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0, new Department(104, "D-DD"),new SimpleDateFormat("yyyy-MM-dd").parse("1994-03-21")));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1, new Department(105, "D-EE"),new SimpleDateFormat("yyyy-MM-dd").parse("1994-03-21")));
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static Integer initId = 1006;
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}
public Collection<Employee> getAll(){
return employees.values();
}
public Employee get(Integer id){
return employees.get(id);
}
public void delete(Integer id){
employees.remove(id);
}
}

DepartmentDao.java: (其中关于数据库的操作仅采用模拟方式实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Repository
public class DepartmentDao {
private static Map<Integer, Department> departments = null;
static{
departments = new HashMap<Integer, Department>();
departments.put(101, new Department(101, "D-AA"));
departments.put(102, new Department(102, "D-BB"));
departments.put(103, new Department(103, "D-CC"));
departments.put(104, new Department(104, "D-DD"));
departments.put(105, new Department(105, "D-EE"));
}
public Collection<Department> getDepartments(){
return departments.values();
}
public Department getDepartment(Integer id){
return departments.get(id);
}
}

控制器EmployeeHandler.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@Controller
public class EmployeeHandler {
@Autowired
private EmployeeDao employeeDao;
@Autowired
private DepartmentDao departmenDao;
@ModelAttribute
public void getEmployee(@RequestParam(value = "id", required = false) Integer id, Map<String, Object> map) {
if (id != null)
map.put("employee", employeeDao.get(id));
}
@RequestMapping(value = "/emp/{id}", method = RequestMethod.DELETE)
public String delete(@PathVariable("id") Integer id) {
employeeDao.delete(id);
return "redirect:/emps";
}
@RequestMapping(value = "/emp", method = RequestMethod.PUT)
public String update(Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {
if (result.getErrorCount() > 0) {
System.out.println("出错了!");
for (FieldError error : result.getFieldErrors()) {
System.out.println(error.getField() + ":" + error.getDefaultMessage());
}
map.put("departments", departmenDao.getDepartments());
return "input";
}
employeeDao.save(employee);
return "redirect:/emps";
}
@RequestMapping(value = "/emp", method = RequestMethod.GET)
public String input(Map<String, Object> map) {
map.put("departments", departmenDao.getDepartments());
/*
* 在input.jsp中使用了SpringMVC的form标签,SpringMVC认为 表单是一定要进行回显的,即便是“第一次来”,
* 所以要new一个 Employee对象放入request域 。
*/
map.put("employee", new Employee());
return "input";
}
@RequestMapping(value = "/emp/{id}", method = RequestMethod.GET)
public String input(@PathVariable("id") Integer id, Map<String, Object> map) {
map.put("departments", departmenDao.getDepartments());
/*
* 在input.jsp中使用了SpringMVC的form标签,SpringMVC认为
* 表单是一定要进行回显的,即便是“第一次来”,所以要获取一个 Employee对象放入request域 。
*/
map.put("employee", employeeDao.get(id));
return "input";
}
@RequestMapping("/emps")
public String list(Map<String, Object> map) {
map.put("employees", employeeDao.getAll());
return "list";
}
}

index.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ 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>index jsp</title>
</head>
<body>
<a href="${pageContext.request.contextPath }/emps">List All
Employees</a>
<br><br>
</body>
</html>

WEB-INF/views/input.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!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>
<form:form action="${pageContext.request.contextPath }/emp"
method="POST" modelAttribute="employee">
<c:if test="${employee.id == null }">
LastName:<form:input path="lastName" />
&nbsp;<form:errors path="lastName"></form:errors>
<br>
<br>
</c:if>
<c:if test="${employee.id != null }">
<form:hidden path="id" />
<input type="hidden" name="_method" value="PUT">
<%-- 对于 _method 不能使用 form:hidden 标签, 因为 modelAttribute 对应的 bean 中没有 _method 这个属性 --%>
<%--
<form:hidden path="_method" value="PUT"/>
--%>
</c:if>
Email:<form:input path="email" />
&nbsp;<form:errors path="email"></form:errors>
<br>
<br>
<%
Map<String, String> genders = new HashMap<String, String>();
genders.put("0", "Female");
genders.put("1", "Male");
request.setAttribute("genders", genders);
%>
Gender:<form:radiobuttons path="gender" items="${genders }" />
<br>
<br>
Birth:<form:input path="birth" />
&nbsp;<form:errors path="birth"></form:errors>
<br>
<br>
Department:<form:select path="department.id" items="${departments }"
itemLabel="departmentName" itemValue="id"></form:select>
<input type="submit" value="Submit" />
</form:form>
</body>
</html>

WEB-INF/views/list.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!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>
<script type="text/javascript"
src="${pageContext.request.contextPath }/scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$(function() {
$(".delete").click(function() {
var href = $(this).attr("href");
$("form").attr("action", href).submit();
return false;
});
})
</script>
</head>
<body>
<form action="" method="POST">
<input type="hidden" name="_method" value="DELETE">
</form>
<c:if test="${empty requestScope.employees }">
no employees information.
</c:if>
<c:if test="${!empty requestScope.employees }">
<table border="1" cellpadding="10" cellspacing="0">
<tr>
<th>ID</th>
<th>LastName</th>
<th>Email</th>
<th>Gender</th>
<th>Birth</th>
<th>Department</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<c:forEach items="${requestScope.employees }" var="emp">
<tr>
<td>${emp.id }</td>
<td>${emp.lastName }</td>
<td>${emp.email }</td>
<td>${emp.gender == 0 ? 'Female' : 'male' }</td>
<td>${emp.birth }</td>
<td>${emp.department.departmentName }</td>
<td><a
href="${pageContext.request.contextPath }/emp/${emp.id }">Edit</a></td>
<td><a class="delete"
href="${pageContext.request.contextPath }/emp/${emp.id }">Delete</a></td>
</tr>
</c:forEach>
</table>
</c:if>
<a href="${pageContext.request.contextPath }/emp">Add New Employee</a>
</body>
</html>

web.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<!-- 配置 org.springframework.web.filter.HiddenHttpMethodFilter: 可以把 POST
请求转为 DELETE 或 PUT 请求 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置 DispatcherServlet -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

springmvc.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 配置要自动扫描的包 -->
<context:component-scan base-package="com.MySpringMVC"></context:component-scan>
<!-- 配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- default-servlet-handler 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,
它会对进入 DispatcherServlet 的请求进行筛查, 如果发现是没有经过映射的请求, 就将该请求交由 WEB 应用服务器默认的 Servlet
处理. 如果不是静态资源的请求,才由 DispatcherServlet 继续处理 一般 WEB 应用服务器默认的 Servlet 的名称都是 default.
若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name 属性显式指定 -->
<mvc:default-servlet-handler />
<!-- 解决 配置了 mvc:default-servlet-handler 或者 mvc:view-controller 后,控制器不能正常映射请求
的问题 还会会默认装配好一个LocalValidatorFactoryBean,用于数据校验 -->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>

lib目录:
image_1b1dmlo7b1cnc1f3sn7fv4cb171g.png-60.7kB

核心知识点

1、SpringMVC的form标签:

  1.1 通过SpringMVC的form标签可以实现将模型数据中的属性和HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显。
  1.2 可以通过SpringMVC的form标签的modelAttribute属性指定绑定的模型属性以进行表单回显。
  1.3 SpringMVC的form标签默认一定要回显表单,若没有指定modelAttribute属性,则默认从request 域对象中读取command的表单bean。如果在 指定的modelAttribute 或者 command 中无法获取到某一属性,则会发生错误。
  
2、处理静态资源:

  2.1 优雅的REST 风格的资源URL 不希望带.html 或.do 等后缀,若将DispatcherServlet请求映射配置为/,则Spring MVC 将捕获WEB 容器的所有请求,包括静态资源的请求(例如使用jQuery),SpringMVC会将他们当成一个普通请求处理,因找不到对应处理器将导致错误。
  
  2.2 上述问题的解决方法为:在SpringMVC的配置文件中添加配置:

1
<mvc:default-servlet-handler/>

其原理是:default-servlet-handler 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求, 就将该请求交由 WEB 应用服务器默认的 Servlet 处理。如果不是静态资源的请求,才由 DispatcherServlet 继续处理。一般情况下,WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name 属性显式指定。

3.数据转换&数据格式化&数据校验

  3.1 数据绑定流程:
  ① Spring MVC主框架将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象;
  ② DataBinder调用装配在Spring MVC上下文中的ConversionService组件进行数据类型转换、数据格式化工作。将Servlet 中的请求信息填充到入参对象中;
  ③ 调用Validator组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData对象;
  ④ Spring MVC抽取BindingResult中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
  Spring MVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下:
  
  image_1b1doigq71hd01kg01e6cqbd150q2j.png-77.5kB
  
  实现源码:
  image_1b1dohstp1v2tjt8rstfue1n9o26.png-236.9kB
  
  3.2 数据转换
  Spring MVC 上下文中内建了很多转换器,可完成大多数Java 类型的转换工作,如下图:
  image_1b1dp9ap5105bn081nmlna5bnj30.png-215.9kB
  
  如果内建的转换器不能满足要求,可以自定义转换器。方法是利用 ConversionServiceFactoryBean 在Spring 的IOC 容器中定义一个 ConversionService,并通过ConversionServiceFactoryBean的 converters 属性注册自定义的类型转换器。Spring 将自动识别出IOC 容器中的ConversionService,并在Bean 属性配置及Spring MVC 处理方法入参绑定等场合使用它进行数据的转换。例如现在要实现一个将String转为Employee对象的转换器:(这里的Employee没有添加birth属性)
  
springmvc.xml:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 将自定义的ConversionService注册到Spring MVC 的上下文中 -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 配置 ConversionService -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>

jsp:

1
2
3
4
5
<form action="testConversionServiceConverer" method="POST" >
<!-- lastname-email-gender-department.id 例如: GG-gg@atguigu.com-0-105 -->
Employee: <input type="text" name="employee"/>
<input type="submit" value="Submit"/>
</form>

controller:

1
2
3
4
5
6
@RequestMapping("/testConversionServiceConverer")
public String testConverter(@RequestParam("employee") Employee employee){
System.out.println("save: " + employee);
employeeDao.save(employee);
return "redirect:/emps";
}

Converter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class EmployeeConverter implements Converter<String, Employee> {
@Override
public Employee convert(String source) {
if(source != null){
String [] vals = source.split("-");
//GG-gg@atguigu.com-0-105
if(vals != null && vals.length == 4){
String lastName = vals[0];
String email = vals[1];
Integer gender = Integer.parseInt(vals[2]);
Department department = new Department();
department.setId(Integer.parseInt(vals[3]));
Employee employee = new Employee(null, lastName, email, gender, department);
System.out.println(source + "--convert--" + employee);
return employee;
}
}
return null;
}
}

  3.3 数据格式化
  对属性对象的输入/输出进行格式化,从其本质上讲依然属于“类型转换”的范畴。Spring 在格式化模块中定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类扩展了GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能。FormattingConversionService拥有一个FormattingConversionServiceFactroyBean工厂类,后者用于在Spring 上下文中构造前者。
  FormattingConversionServiceFactroyBean内部已经注册了:

  • NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用@NumberFormat注解

  • JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用@DateTimeFormat注解

  装配了FormattingConversionServiceFactroyBean后,就可以在Spring MVC 入参绑定及模型数据输出时使用注解驱动了。mvc:annotation-driven标签 默认创建的ConversionService 实例即为 FormattingConversionServiceFactroyBean
  例如,我们在Employee.java中的birth属性上添加了注解:@DateTimeFormat(pattern=”yyyy-MM-dd”),即指定了日期输入的格式。此外,还可通过@NumberFormat注解对类似数字类型的属性进行格式化标注,例如

1
2
  @NumberFormat(pattern="#,###,###.#")
private Float salary;

  3.4 数据校验
  JSR 303是Java 为Bean 数据合法性校验提供的标准框架,它已经包含在JavaEE 6.0中。JSR 303通过在Bean 属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean 进行验证。详细注解如下图:
  image_1b1ds94ej1m5d1c9h2oo118g13vc3t.png-208.7kB
  
  hibernate Validator是JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
  image_1b1dsabituub1vpsv23grg1f2f4a.png-159.3kB
  
Spring MVC 数据校验:

  Spring 4.0拥有自己独立的数据校验框架,同时支持JSR 303标准的校验框架。
  Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC 中,可直接通过注解驱动的方式进行数据校验。
  Spring 的LocalValidatorFactroyBean既实现了Spring 的Validator 接口,也实现了JSR 303 的Validator接口。只要在Spring 容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean 中。mvc:annotation-driven标签 会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@valid注解即可让Spring MVC 在完成数据绑定后执行数据校验的工作。
  Spring 本身并没有提供JSR303 的实现,所以必须将JSR303 的实现者的jar 包放到类路径下:
  image_1b1dsi76s5j875d19k014n2nao4n.png-10.6kB
  
  Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型,这两个类都位于org.springframework.validation包中。需校验的Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参
  例如,在上述的EmployeeHandler类的save方法中即声明了需要对employee入参进行校验,并将校验结果存入result中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {
if (result.getErrorCount() > 0) {
System.out.println("出错了!");
for (FieldError error : result.getFieldErrors()) {
System.out.println(error.getField() + ":" + error.getDefaultMessage());
}
map.put("departments", departmenDao.getDepartments());
return "input";
}
employeeDao.save(employee);
return "redirect:/emps";
}

其中对employee的哪些字段进行何种校验,Employee.java中已经标注了,例如

1
2
@NotEmpty
private String lastName;

标注了lastName字段不能为null。

在目标方法中获取校验结果:
  上面save方法的代码已经介绍了如何利用校验结果的信息在目标方法中进行打印。
  
在页面上显示错误:
  Spring MVC 除了会将表单/命令对象的校验结果保存到对应的BindingResult或Errors 对象中外,还会将所有校验结果保存到“隐含模型”,即implicitModel。隐含模型中的所有数据最终将通过HttpServletRequest的属性列表暴露给JSP 视图对象,因此在JSP 中可以获取错误信息。例如,如果上述的lastName字段为null,则可以在跳转的页面input.jsp中通过如下代码获取错误信息。

1
  <form:errors path="lastName"></form:errors>

提示消息的国际化:
   每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的FieldError对象。当一个属性校验失败后,校验框架会为该属性生成4 个消息代码,这些代码以校验注解类名为前缀,结合modleAttribute、属性名及属性类型名生成多个对应的消息代码:例如User 类中的password 属性标准了一个@Pattern 注解,当该属性值不满足@Pattern 所定义的规则时, 就会产生以下4 个错误代码:

  • Pattern.user.password
  • Pattern.password
  • Pattern.java.lang.String
  • Pattern

  当使用Spring MVC 标签显示错误消息时,Spring MVC 会查看WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。例如,我们要定制lastName字段的验证出错的错误消息:
  
创建国际化资源文件 i18n.properties:(其中“LastName\u4E0D\u80FD\u4E3A\u7A7A”是输入汉字“LastName不能为空”自动转换为ISO-8859-1编码的结果)

1
NotEmpty.employee.lastName=LastName\u4E0D\u80FD\u4E3A\u7A7A

在springmvc.xml中配置国际化资源文件

1
2
3
4
5
<!-- 配置国际化资源文件 -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>

那么如果lastName字段为null,错误信息将会是:LastName不能为空。
  
  若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:

  • required:必要的参数不存在。如@RequiredParam(“param1”) 标注了一个入参,但是该参数不存在。
  • typeMismatch:在数据绑定时,发生数据类型不匹配的问题
  • methodInvocation:Spring MVC。 在调用处理方法时发生了错误–注册国际化资源文件。

4、关于mvc:annotation-driven
  mvc:annotation-driven 会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter与ExceptionHandlerExceptionResolver三个bean。还将提供以下支持:

  • 支持使用ConversionService实例对表单参数进行类型转换(不加的话就没有ConversionService)
  • 支持使用@NumberFormat、@DateTimeFormat注解完成数据类型的格式化
  • 支持使用@Valid注解对JavaBean 实例进行JSR 303验证
  • 支持使用@RequestBody和@ResponseBody注解

5、关于@InitBinder注解
  由@InitBinder标识的方法,可以对WebDataBinder对象进行初始化。WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean 属性的绑定。
  @InitBinder方法不能有返回值,它必须声明为void。
  @InitBinder方法的参数通常是WebDataBinder。
  例如,如下代码声明了不将请求参数中的lastName属性绑定到WebDataBinder中:

1
2
3
4
5
//lastName不进行赋值,(不自动绑定对象中的lastName属性,另行处理) user的角色赋值的时候可能会用到
@InitBinder
public void initBinder(WebDataBinder binder){
binder.setDisallowedFields("lastName");
}