## 十二、Restful_CRUD
### 概述
```markdown
利用SpringMVC做一个符合Rest风格的CRUD(增删改查)。
C_Create 创建
R_Retrieve 查询
U_Update 更新
D_Delete 删除
这里为了演示方便,就不使用数据库来保存数据了,由Map、List之类的来代替保存数据。
```
Rest风格的实现
![2-4](图片02/2-4-Rest风格.PNG)
注意一点,高版本Tomcat中,jsp页面不支持特殊请求方式,所以需要【isErrorPage="true"】
### 1、环境搭建
#### 1.1、项目工程结构
![1](图片02/1-环境搭建.PNG)
#### 1.2、具体实现步骤
```markdown
1、创建javaweb工程
2、导包
web/WEB-INF/lib/
commons-logging-1.1.3.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-aop-4.0.0.RELEASE.jar
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar
taglibs-standard-impl-1.2.1.jar
taglibs-standard-spec-1.2.1.jar
3、配置
web/WEB-INF/web.xml
前端控制器【DispatcherServlet】
字符编码过滤器【CharacterEncodingFilter】
支持Rest风格转换的过滤器【HiddenHttpMethodFilter】
conf(源码包)/springmvc.xml
扫描组件【context:component-scan】
视图解析器【InternalResourceViewResolver】(它是默认有的,可以不做配置)
4、补充
web/WEB-INF/pages/success.jsp
web/index.jsp
controller
bean
dao
```
#### 1.3、bean
如下bean(entities),自己补充完整。(setter、getter、toString、无参构造、全参构造)
##### Employee
```java
private Integer id;
private String departmentName;
```
##### Department
```java
private Integer id;
private String lastName;
private String email;
private Integer gender; // 1 male, 0 female
private Department department;
```
#### 1.4、dao
##### EmployeeDao
```java
package com.myself.dao;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.myself.bean.Department;
import com.myself.bean.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
/**
* EmployeeDao:操作员工的CRUD
*/
@Repository
public class EmployeeDao {
// 数据存储在Map
private static Map employees = null;
@Autowired
private DepartmentDao departmentDao;
// 初始化时新建员工,相当于数据库中已经存有的记录(毕竟这里不使用数据库的原因:为了演示方便才使用这种方式代替而已。)
static{
employees = new HashMap();
employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1, new Department(101, "D-AA")));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1, new Department(102, "D-BB")));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0, new Department(103, "D-CC")));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0, new Department(104, "D-DD")));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1, new Department(105, "D-EE")));
}
// 新增员工的初始id(因为你已经定义到了1005,所以要从1006开始)
private static Integer initId = 1006;
/**
* 员工更新、保存两者结合的方法
* @param employee
*/
public void save(Employee employee){
// 如果没有id,那就新增员工
if(employee.getId() == null){
employee.setId(initId++);
}
// 根据部门id单独查出部门信息并设置到员工对象中,页面只需提交部门id
employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
// 如果已经有id,那就在原来基础上更新
employees.put(employee.getId(), employee);
}
/**
* 返回所有员工的值,也就是:查询所有的员工
* @return
*/
public Collection getAll(){
return employees.values();
}
/**
* 根据id查询某个员工
* @param id
* @return
*/
public Employee get(Integer id){
return employees.get(id);
}
/**
* 删除某个员工
* @param id
*/
public void delete(Integer id){
employees.remove(id);
}
}
```
##### DepartmentDao
```java
package com.myself.dao;
import com.myself.bean.Department;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* DepartmentDao:操作部门的CRUD
*/
@Repository
public class DepartmentDao {
private static Map departments = null;
static{
departments = new HashMap();
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"));
}
/**
* 返回所有部门的值,也就是:查询所有部门
* @return
*/
public Collection getDepartments(){
return departments.values();
}
/**
* 查询某个部门
* @param id
* @return
*/
public Department getDepartment(Integer id){
return departments.get(id);
}
}
```
#### 1.5、最终的效果
##### 查询操作
![2-1](图片02/2-1-查询.PNG)
##### 添加操作
![2-2](图片02/2-2-添加.PNG)
##### 修改、删除操作
对于删除:点击删除完成后,来到列表页面。
![2-3](图片02/2-3-修改、删除.PNG)
### 2、【查询】员工列表展示
#### 2.1、流程
员工列表展示(查询所有员工)
```
-->访问index.jsp
--> 直接发送请求/emps
--> 控制器查询所有员工
--> 查询所得数据放在请求域中
--> 转发到list.jsp页面展示
```
#### 2.2、首页【index】
就三行代码。。
```jsp
```
#### 2.3、请求处理器【EmployeeController】
```java
package com.myself.controller;
import com.myself.bean.Employee;
import com.myself.dao.EmployeeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Collection;
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
/**
* 处理“查询所有员工”的请求
* @return
*/
@RequestMapping("/emps")
public String getEmps(Model model){
Collection all = employeeDao.getAll();
model.addAttribute("emps",all);
return "list";
}
}
```
#### 2.4、展示页【list】
```jsp
员工列表员工的详细信息表
id | lastName | gender | department | edit | delete | |
---|---|---|---|---|---|---|
${emp.id} | ${emp.lastName} | ${emp.email} | ${emp.gender==0?"女":"男"} | ${emp.department} | edit | delete |
```
### 3、【添加】来到添加页面,使用原生的方式完成添加
#### 3.1、流程
员工添加
```
-->在list页面点击“员工添加”
-->查询出所有部门信息要展示在页面(来到add页面之前需要提前做的)
-->来到add.jsp页面
-->输入员工数据,点击保存
-->处理器收到保存请求,保存员工数据
-->保存完成后,回到list列表页面
```
#### 3.2、list页面
```jsp
```
#### 3.3、EmployeeController请求处理器
```java
/**
* 去员工添加页面,
* 但在去之前需要查询出所有部门信息在add页面的下拉列表框中进行展示,
* 添加的部门名不应该手动写,应该选择数据库中存在的才是对。
* @return
*/
@RequestMapping("/toaddpage")
public String toAddPage(Model model){
// 1、先查出所有部门
Collection departments = departmentDao.getDepartments();
// 2、放在请求域中
model.addAttribute("depts",departments);
// 3、去添加页面
return "add";
}
```
#### 3.3、add页面
要使用c:forEach遍历,需要导入核心库core
以下是原生的办法保存表格提交的数据
```jsp
员工添加页面员工添加
lastName:
email:
gender:
男:
女:
dept:
${deptItem.departmentName}
```
### 4、【添加】使用SpringMVC表单标签完成添加
![3](图片02/3-表单标签.PNG)
#### 4.1、表单标签库的使用
```jsp
action:可以不写,因为会自动匹配的,但建议写!
path:就是原来input的name项,需要写,他有2个作用
1)当做原生的name项
2)自动回显隐含模型中某个对象对应的这个属性的值
表单标签中也支持原生的各种标签,例如--%>
lastName:
email:
gender:
男:
女:
dept:
原来的select:
${deptItem.departmentName}
Spring的select:
items="${depts}" 指定要遍历的集合,会自动遍历。遍历出的每一个元素是一个Department对象
itemLabel="属性名" 该对象的哪个属性作为option标签体的值(也就是显示出来的)
itemValue="属性名" 该对象的哪个属性作为要提交的value值
--%>
```
#### 4.2、注意事项
```markdown
1)SpringMVC认为,表单数据中的每一项,最终都是要回显的。
path指定的是一个属性,这个属性是从隐含模型,也就是请求域中取出来的某个对象中的属性
path指定的每一个属性,在请求域中必须存在那么一个对象拥有指定的每一个属性,这个对象就是command(这是SpringMVC表单标签的要求)
command对象属性与path相对应的值,也就相当于默认值
model.addAttribute("command",new Employee(null,"张三","haha@myself.com",0,departmentDao.getDepartment(105)));
如果没有这个command对象的话,会报Http-500异常:
IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
总之,请求域中必须存在command对象。
2)后来,出现了modelAttribute属性,
它可以告诉SpringMVC不用再去找command对象了,由我指定的对象代替了command对象:modelAttribute="employee",
指定modelAttribute后,就不能再写command对象了,否则报错,
而我指定的对象同样也必须全部匹配上path代表的每一属性。
path指定的每一个属性,都代表请求域中某个对象的属性
path="lastName"
path="gender"
path="email"
path="department.id"
所以,springmvc要求,必须存在那么一个对象在请求域中,该对象的属性必须与path属性都有所对应,
这个对象默认是command对象,当然,使用modelAttribute后可以自定义一个对象,自定义的对象必须都有path代表的属性,
自定义的对象的属性的个数可以比path代表的所有属性个数加起来的都多,但不能比它少。
自定义属性至少要有以上的这4钟属性【lastName、gender、email、department】(department是级联属性)
记住,springmvc的表单标签的path当做原生的name项看待就好理解多了。
SpringMVC的表单标签
原生的标签
```
#### 4.3、请求处理器类
因为path代表的是请求域中某个对象的属性,故必须存在这个对象,所以这里将那个对象存入请求域中。
```java
@RequestMapping("/toaddpage")
public String toAddPage(Model model){
Collection departments = departmentDao.getDepartments();
model.addAttribute("depts",departments);
// 默认的
// 表单标签需要一个command对象在请求域中
// 写的每一个参数都会被当做默认值
//model.addAttribute("command",new Employee(null,"张三","haha@myself.com",0,departmentDao.getDepartment(105)));
// 自定义的
// 自己指定对象后,不再需要command对象了
// 没有指定参数默认,则全部为null
model.addAttribute("employee",new Employee());
return "add";
}
```
### 5、【添加】完成添加
index.jsp
```jsp
/* 获取项目路径 */
pageContext.setAttribute("ctp",request.getContextPath());
%>
/* 在页面中,推荐写绝对路径:action="项目路径/请求" */
```
请求处理器类
```java
/**
* 保存员工
* @param employee
* @return
*/
@RequestMapping(value = "/emp", method = RequestMethod.POST) // 只接受post
public String addEmp(Employee employee){
System.out.println("要添加的员工:" + employee);
employeeDao.save(employee);
// 返回列表页面,重定向到查询所有员工的请求(不用携带数据,所以不用转发)
return "redirect:/emps";
}
```
### 6、【修改】来到员工回显页面
![4](图片02/4-回显.PNG)
#### 6.1、list页面
```jsp
/* 项目路径 */
pageContext.setAttribute("ctp",request.getContextPath());
/* 页面中建议使用绝对路径,即所有请求要加上:项目路径 */
%>
```
#### 6.2、请求处理器类
```java
/**
* 来到回显页面
*/
@RequestMapping(value = "/emp/{id}", method = RequestMethod.GET)
public String getEmp(@PathVariable("id") Integer id, Model model){
// 1、查出员工信息,放在请求域中(页面用到:modelAttribute="employee")
Employee employee = employeeDao.get(id);
model.addAttribute("employee", employee);
// 2、继续查出部门信息放在隐含模型中(页面用到:items="${depts}")
Collection departments = departmentDao.getDepartments();
model.addAttribute("depts",departments);
return "edit";
}
```
#### 6.3、edit页面
```jsp
员工修改页面员工修改页面
email:
gender:
男:
女:
dept:
```
### 7、【修改】完成修改
流程
![5](图片02/5-修改.PNG)
#### 7.1、edit.jsp
```jsp
...
```
#### 7.2、请求处理器类
```java
/**
* 修改员工信息
* put请求
*
* 修改操作,一般要求前端要传来id,相当于主键一样,通过主键修改数据库记录的,拿id的方法:
* 法一:@PathVariable("id") Integer id
* 通过注解【@PathVariable】获取Employee的id
* 听雷神说以前还是可以取到的,但现在这种方法已经取不到了!!!(放弃)
* 法二:
* 在前端写个标签传入id,后端一定取到id。(适用)
*/
@RequestMapping(value = "/emp/{id}", method = RequestMethod.PUT)
//public String updateEmp(Employee employee){ // 没取id,id=null
//public String updateEmp(Employee employee, @PathVariable("id") Integer id){ // 通过注解取id,还是取不到id,id=null(以前是可以取到的)
//public String updateEmp(Employee employee){ // 在前端传入隐藏的id,可以取到id
public String updateEmp(@ModelAttribute("employee") Employee employee){//提前取出 数据库中已经有的对象,避免null覆盖
System.out.println("要修改的员工信息:" + employee);
return "redirect:/emps";
}
@ModelAttribute // 提前查询出员工并存入隐含模型中,以便目标方法获取员工对象
public void myModelAttribute(@RequestParam(value = "id", required = false) Integer id, Model model){
if (id != null){
Employee employee = employeeDao.get(id);
model.addAttribute("employee", employee);
}
System.out.println("myModelAttribute()方法运行了...");
}
```
#### 7.3、提前运行容易引发的问题
只要是目标方法(存在多个)执行,那么【@ModelAttribute】修饰的方法都会提前执行
```java
/*
由于这个方法提前于目标方法执行,所以只要是目标方法要执行之前,这个方法都会先执行
如果没有if判断,你传入一个数据库中没有的id,那么,查询到的employee对象就是null,
倘若请求的是保存对象操作,那么传入数据库中的就是null,就会报空指针异常,
所以,一定要if判断
*/
@ModelAttribute
public void myModelAttribute(@RequestParam(value = "id", required = false) Integer id, Model model){
//if (id != null){
Employee employee = employeeDao.get(id);
model.addAttribute("employee", employee);
//}
System.out.println("myModelAttribute()方法运行了...");
}
// 如果请求时保存数据的请求,就会调用保存的方法
employeeDao.save(employee); // 传入null
// dao层的具体实现
public void save(Employee employee){
if(employee.getId() == null){ // null调用方法,此时就会报空指针异常
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}
```
### 8、【删除】完成删除
流程
![6](图片02/6-删除.PNG)
list.jsp
```jsp
```
请求处理器类
```java
/**
* 删除员工
* @param id
* @return
*/
@RequestMapping(value = "/emp/{id}", method = RequestMethod.DELETE)
public String deleteEmp(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
```
### 9、【删除】引入jQuery文件与静态资源访问的问题
看参考视频。
```JSP
一个delete请求写一个form表单(因为要post请求转为delete请求),明显是不合理的,
应该与edit一样,一个超链接的形式才对,
使用jQuery技术绑定单击事件,可以将超链接由form表单间接的代替。
当你点击delete超链接时,禁用掉默认行为,也就是不让它href跳转发送请求,
接下来把表单的action地址变为与超链接的href地址一模一样,
然后用jQuery提交form表单,也就是action跳转发送请求。
偷梁换柱:点击超链接,原本提交超链接的href,但最后由jQuery提交表单action。
注意:
在写前端控制器时,我们拦截了所有请求【/】,所以应该由Tomcat处理的jquery-1.9.1.min.js静态文件被拦截了,如下错误:
No mapping found for HTTP request with URI [/7_SpringMVC_crud/scripts/jquery-1.9.1.min.js] in DispatcherServlet with name 'springmvc'
需要配置springmvc.xml。
由于遍历多次会有多个,所以标签的标识符不应该用id,要用class,
因为id必须唯一,即:每个应该有唯一一个id
--%>
delete表单放在forEach循环外边,减少浏览器压力,否则每次循环都生成一个form表单。
这里action不用写,脚本重新赋值action了
--%>
$(function () {
$(".delBtn").click(function () { // 绑定单击事件
$("#deleteForm").attr("action", this.href); // 1、改变表单的action指向
/*
关于jQuery的函数:
$(xxx).click(); // 直接调用函数:执行某个(单击)动作
$(xxx).click(function(){...}); // 调用函数并给函数传入回调函数:执行某个动作(单击)时才会执行回调函数
例如:
$(xxx).submit(); // 提交请求
$(xxx).submit(function(){ // 提交的时候执行回调函数
return false; // 明显这个回调函数功能是:false禁用默认行为,也就是不提交请求了
});
*/
$("#deleteForm").submit(); // 2、提交表单,表单请求执行
return false; // 3、禁用默认行为,不让请求发送了(也就是单击事件此处往后,不再执行了,也就可以阻止a发送href了,但此之前,表单的请求已经完成了。)
});
});
```
springmvc.xml
```xml
```
请求处理器类
```java
/**
* 删除员工
* @param id
* @return
*/
@RequestMapping(value = "/emp/{id}", method = RequestMethod.DELETE)
public String deleteEmp(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
```
## 十三、数据绑定
### 1、数据绑定原理及其思想
参考视频(涉及源码)以及ppt。
##### 数据绑定流程
![7-1](图片02/7-1-数据绑定流程.PNG)
![7-2](图片02/7-2-数据绑定流程.PNG)
##### 雷神笔记
![7-3](图片02/7-3-笔记.PNG)
##### 数据格式化
![8-1](图片02/8-1-数据格式化.PNG)
### 2、自定义类型转换器
参考视频。
##### SpringMVC支持的转换器
![8-2](图片02/8-2-SpringMVC支持的转换器.PNG)
##### 自定义类型转换
```
因为SpringMVC默认的类型转换器只有String转为基本类型(例如:integer、boolean)的转换器,
并没有转换为我们自定义类型(例如:student、user、book)的转换器,所以我们需要自定义。
请求【/user?username=zs&age=18&gender=1】,默认都是String类型变为基本类型:
请求参数(Stirng)-->基本类型
username(String)-->username(String)
age(String)-->age(Integer)
gender(String)-->gender(Boolean)
你会发现没有:
请求参数(String)-->自定义类型
所以,需要自定义类型转换器。
```
##### 雷神笔记
![8-3](图片02/8-3-自定义类型转换.PNG)
##### 自定义步骤的代码实现
###### list.jsp
```jsp
```
###### 请求处理器类
```java
/**
* 传递过来的是啥?
* 是:quickadd?empAdmin-admin@qq.com-1-101
* 也就相当于:@RequestParam("empinfo") Employee employee;
* request.getParameter("empinfo"); // 传来的是String
* 但你要封装到Employee中,明显是没有【String-->Employee】的转换器的,SpringMVC只提供【String-->基本类型】的转换器,
* 此时,就应该自定义类型转换器。
*/
@RequestMapping("/quickadd")
//public String quickAdd(Employee employee){
public String quickAdd(@RequestParam("empinfo") Employee employee){
System.out.println("封装的对象:" + employee);
employeeDao.save(employee);
return "redirect:/emps";
}
```
###### 【1】写一个自定义类型Converter
```java
package com.myself.component;
import com.myself.bean.Employee;
import com.myself.dao.DepartmentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
/**
* 自定义类型转换器
* String --> Employee
* 需要实现接口:Converter
* S:source
* T:target
* 将S转为T(也就是:String --> Employee)
*/
//public class MyStringToEmployeeConverter implements Converter {
public class MyStringToEmployeeConverter implements Converter { // 将S转为T
@Autowired
DepartmentDao departmentDao;
/**
* 自定义转换规则,将前端传入的String转化为Employee对象。
*
* @param source 前端传入:String
* @return target 转换的目标形式:Employee
*/
@Override
public Employee convert(String source) {
/*
页面提交的要转化的字符串:quickadd?empAdmin-admin@qq.com-1-101
*/
System.out.println("页面提交的要转化的字符串:" + source);
Employee employee = new Employee();
if (source.contains("-")){
String[] split = source.split("-"); // 每个参数用“-”分割,然后封装进employee对象
employee.setLastName(split[0]); // lastName
employee.setEmail(split[1]); // email
employee.setGender(Integer.parseInt(split[2])); // gender
employee.setDepartment(departmentDao.getDepartment(Integer.parseInt(split[3]))); // 根据部门id查询部门,封装为department并存入employee
}
return employee; // 在上面将employee准备好,就可以返回了。
}
}
```
###### 【2】把Converter放到ConversionService里面
springmvc.xml
```xml
```
###### 【3】让SpringMVC用我们自定义的ConversionService
springmvc.xml
```xml
```
### 3、解析【mvc:annotation-driven】标签
这个讲的源码好多,自己看视频吧。
![9](图片02/9-解析标签.PNG)
雷神笔记
![10-1](图片02/10-1-解析.PNG)
### 4、日期格式化
数据格式化
![10-2](图片02/10-2-数据格式化.PNG)
日期
![11-2](图片02/11-2-日期格式化.PNG)
数值
![11-3](图片02/11-3-数值格式化.PNG)
格式化
![11](图片02/11-格式化.PNG)
add.jsp
```jsp
birth:
```
Employee.java
```java
//private Date birth;
// 日期
// 显示日期的形式为:2020-5-20
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
// 数值
// 显示工资的形式为:¥10,000.75
@NumberFormat(pattern = "#,###.##")
private Double salary;
```
list.jsp
```jsp
birth${emp.birth}```
springmvc.xml
```xml
```
## 十四、数据校验
![12](图片02/12-数据校验.PNG)
![14](图片02/14-hibernate.PNG)
JSR303是SpringMVC的提供的校验规范,就如jdbc也是一种规范一样。
![13](图片02/13-JSR303.PNG)
### 1、校验注解
##### 第一步,添加jar包
WEB-INF/lib/
```markdown
hibernate validator框架数据校验的核心jar包
hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
核心包依赖的jar包
classmate-0.8.0.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar
```
##### 第二步,需要给Javabean的属性添加上校验注解
employee.java
```java
@NotEmpty // 不能为空
@Length(min = 6, max = 18) // 规定字符长度
private String lastName;
@Email // 是合法的email格式
private String email;
@Past // 必须是过去的时间,毕竟是生日
//@Future // 将来时间
private Date birth;
```
##### 第三步,在SpringMVC封装对象的时候,告诉SpringMVC这个Javabean需要校验
加注解 @Valid
```java
public String addEmp(@Valid Employee employee){...}
```
##### 第四步,如何知道校验结果?
给需要检验的Javabean后面紧跟一个 BindingResult(这两个参数之间必须是相邻的,不能穿插其它参数在中间。),这个BindingResult就是封装了前面Javabean的校验结果。
```java
public String addEmp(@Valid Employee employee, BindingResult result){...}
```
##### 第五步,获取判断结果并判断逻辑
```java
/**
* 保存员工
*/
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult result){
boolean hasErrors = result.hasErrors(); // 获取是否有校验错误
if (hasErrors){ // 验证失败,就重新返回添加页面
System.out.println("有校验错误...");
return "add";
} else { // 验证成功,继续往下执行
employeeDao.save(employee);
return "redirect:/emps"; // 返回列表页面,重定向到查询所有员工的请求(不用携带数据,所以不用转发)
}
}
```
##### 第六步(1)【特殊表单:表单标签】来到页面可以取出校验错误的信息,并显示出来
用表单(form:errors)标签取出错误信息【特殊表单】
add.jsp
```jsp
lastName:
email:
birth:
```
##### 第六步(2)、【普通(原生)表单】可以将校验错误信息放在请求域中,然后直接去页面获取
请求处理器类
```java
/**
* 保存员工
*/
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult result, Model model){
boolean hasErrors = result.hasErrors();
if (hasErrors){
List fieldErrors = result.getFieldErrors(); // 拿到错误信息
Map errorMap = new HashMap(); // 将错误信息以键值对形式存入map中
for (FieldError fieldError : fieldErrors) {
//System.out.println("错误消息提示:" + fieldError.getDefaultMessage());
//System.out.println("错误字段是:" + fieldError.getField());
//System.out.println("整个错误信息是:" + fieldError);
//System.out.println("--------------------------------------------------------------");
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage()); // 存入map
}
model.addAttribute("errorInfo",errorMap); // 最后再将map存入请求域中,如此就能够在页面获取到
System.out.println("有校验错误...");
return "add";
} else {
employeeDao.save(employee);
return "redirect:/emps";
}
}
```
add.jsp
用el表达式取出错误信息【原生表单】,与特殊表单的效果一样。
将 form:form 表单标签换成普通的 form 表单也行,因为是使用el表达式取出的错误信息
```jsp
lastName: ${errorInfo.lastName}
email: ${errorInfo.email}
birth: ${errorInfo.birth}
```
### 2、自定义国际化错误信息的显示
```
默认情况下,框架就已经帮我们写好了错误信息提示,但实际开发中,我们都是需要自定义错误信息的,
同时,有些框架也已经帮我们处理了国际化问题,但有些却没有处理,
此时,我们就需要自定义错误提示信息、并自己处理国际化问题。
```
![16](图片02/16-提示消息的国际化.PNG)
![16-2](图片02/16-2-提示消息的国际化.PNG)
###### 第一步:编写国际化的资源文件
key:错误代码
value:错误的信息提示内容
自己使用在线网站的Unicode转化工具将中文转化(百度上很多)
errors_zh_CN.properties、errors_en_US.properties
```properties
# key必须要写错误代码的形式,下面有
# errors_zh_CN.properties
Email.email=\u90ae\u7bb1\u683c\u5f0f\u4e0d\u5bf9~~
NotEmpty=\u4e0d\u80fd\u4e3a\u7a7a~~
# Length.java.lang.String=\u957f\u5ea6\u4e0d\u5bf9~~
Length.java.lang.String=\u957f\u5ea6\u4e0d\u5bf9\uff0c\u5fc5\u987b\u5728 {2} \u548c {1} \u4e4b\u95f4~~
Past=\u65f6\u95f4\u5fc5\u987b\u662f\u8fc7\u53bb\u7684~~
typeMismatch=\u751f\u65e5\u683c\u5f0f\u4e0d\u6b63\u786e~~
# errors_en_US.properties
Email.email=email incorrect~~
NotEmpty=must not empty~~
# Length.java.lang.String=length incorrect~~
Length.java.lang.String=length incorrect, {0} must between {2} and {1}~~
Past=must a past~~
typeMismatch=incorrect birthday format~~
# 精确与模糊问题,能匹配上精确的,就会忽略相对模糊的(以下都是关于@Email校验的)。
Email.email=XXX // 选择它
Email=YYY // 忽略掉
```
编写国际化文件里的key有要求,key不能乱写,要写为错误代码的形式如下:
错误代码可以在错误信息中看出来,若不知道写那些key的话,就在错误信息中找!!!!
```java
/*
每一个字段发生错误后,都会有自己的错误代码,
而国际化文件中错误消息的key,必须对应一个错误代码。
*/
-------------------------------------------------------更多错误代码可以在错误信息中看到
错误代码(越往下越模糊匹配):
codes [
Length.employee.lastName, // 校验规则 . 隐含模型中这个对象的key . 对象的属性(哪个对象哪个属性)
Length.lastName, // 校验规则 . 属性名(哪个属性)
Length.java.lang.String, // 校验规则 . 属性类型(啥类型)
Length]; // 校验规则
-------------------------------------------------------
例如:
1、如果是隐含模型中employee对象的lastName属性字段发生了@Length校验错误,就会生成【Length.employee.lastName】的错误代码。
2、所有lastName属性字段(lastName属性字段不只一个Javabean有)只要发生@Length校验错误,就报错误代码【Length.lastName】。
3、只要是String类型发生@Length错误,就报错误代码【Length.java.lang.String】
4、只要发生了@Length校验错误,就报错误代码【Length】。
// 在国际化配置文件中,以上任意一种符合错误代码的key都行,就不会报错了,如下:
aa=lastName between 6 and 18 // 这里乱写key=aa,会报错,下面的都是正确的
Email.email=email incorrect~~
NotEmpty=must not empty~~
Length.java.lang.String=length incorrect~~
Past=must a past time~~
// 加注解的属性字段:
@NotEmpty
@Length(min = 6, max = 18)
private String lastName;
private String email;
@Past
private Date birth;
```
###### 第二步:让SpringMVC管理国际化文件
![15](图片02/15-资源文件.PNG)
springmvc.xml
```xml
```
###### 第三步:来到页面取出错误提示信息
form:errors
add.jsp
```jsp
birth:
```
###### 第四步:高级国际化
```markdown
# 高级国际化(动态传入消息参数)
属性字段
@Length(min = 6, max = 18)
private String lastName;
国际资源配置文件的key,value
Length.java.lang.String=length incorrect, {0} must between {2} and {1}~~
参数问题
{0} 永远是属性名,即lastName
{1}、{2} 从1往后才是注解的参数值,排序规则不是哪个参数写在前面就是1号,而是按字母顺序排【min < max】,所以max在前为1。
```
### 3、message指定错误消息
```java
其实,错误消息提示可以直接使用message即可,不需要写那些配置文件了,
但注意,只写message虽然可以提示错误消息,但它却不能做到国际化了,
实际开发中也用不到国际化!!!一般直接用message即可,但如果有国际化需求时,就不用message了,会去使用配置国际化文件。
@Email(message = "亲,邮箱格式错了哟~~")
private String email;
```
拓展:自己看看如何将原生的提示与国际化的提示变为一致的。
```jsp
email: ${errorInfo.email}
也就是将【】和【${errorInfo.email}】变为一样!
后者属于原生的方式,它是将校验信息预先存入请求域中后才可以从页面中拿到的,自己思考实现吧!
```
## 十五、SpringMVC支持Ajax、SpringMVC其它内容
![17](图片02/17-ajax.PNG)
### 1、SpringMVC-ajax
##### 第一步:导Jackson的3个包
```
jackson-annotations-2.1.5.jar
jackson-core-2.1.5.jar
jackson-databind-2.1.5.jar
```
##### 第二步:【响应体】@ResponseBody
@ResponseBody标在方法上,将数据返回到响应体中,若是对象,就一json形式返回。
AJaxTestController.java
```java
package com.myself.controller;
import com.myself.bean.Employee;
import com.myself.dao.EmployeeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Collection;
/**
* 对于服务器来说,给前端返回json数据即可。
*/
@Controller
public class AJaxTestController {
@Autowired
EmployeeDao employeeDao;
@ResponseBody // 注解【@ResponseBody】将响应的数据放在响应体中。
@RequestMapping("/getallajax")
public Collection ajaxGetAll(){
Collection all = employeeDao.getAll();
return all; // 如果是对象,Jackson包会自动将对象转化为json格式返回。
}
}
```
##### 第三步:Javabean
```java
/**
* json的注解有很多,自己看左边的lib包:jackson-annotations-2.1.5.jar
* @JsonIgnore 忽略哪些字段,不将其放到json响应体中响应给前端
* @JsonFormat(pattern = "yyyy-MM-dd") 以某种格式将数据展现在json中
* ...
*/
public class Employee {
@JsonIgnore
private Integer gender;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birth = new Date();
}
```
### 2、ajax获取所有员工
emps.jsp
```jsp
emps pagepageContext.setAttribute("ctp", request.getContextPath());
%>
不刷新页面的情况下获取所有员工信息,
发请求,观察时间不变化即可验证是局部刷新。
--%>
$("a:first").click(function () {
$.ajax({ // 1、发送ajax请求,获取所有员工
url:"${ctp}/getallajax",
type:"GET",
success:function (data) {
console.log(data);
}
});
return false; // 禁用默认行为
});
```
### 3、【请求体】@RequestBody
```java
/**
* 注解【@RequestBody】
* 获取一个请求的请求体(只有post请求才有请求体)
* 注解【@RequestParam】
* 获取请求参数
*/
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){
System.out.println("请求体:" + body);
return "success";
}
```
testOther.jsp
```jsp
other pagepageContext.setAttribute("ctp", request.getContextPath());
%>
```
### 4、发送json数据给服务器
testOther.jsp
```jsp
$("a:first").click(function () {
var emp = {lastName:"张三", email:"aaaa@aa.com", gender:0};
var empStr = JSON.stringify(emp);
//alert(typeof emp); // object
//alert(typeof empStr); // string
$.ajax({ // 发送ajax请求,携带json数据
url:"${ctp}/testRequestBody02",
type:"POST",
data:empStr,
contentType:"application/json",
success:function (data) {
alert(data);
}
});
return false; // 禁用默认行为
});
```
AjaxTestController.java
```java
/**
* @ResponseBody: 响应体,把对象转为json形式,并返回给浏览器。
* @RequestBody: 请求体,可以接收json数据,并将其封装为对象。
*/
@RequestMapping("/testRequestBody02")
public String testRequestBody(@RequestBody Employee employee){ // 请求体直接封装为对象
System.out.println("请求体:" + employee);
return "success"; // 响应给前端的是页面:success.jsp(如果类上加了注解【@ResponseBody】,那么返回的将会是字符串"success")
}
```
### 5、【请求头】的获取 HttpEntity
testOther.jsp
```jsp
```
AJaxTestController.java
```java
/**
* 参数写 HttpEntity ,
* 获取的除了有请求体以外,还获取到请求头。
*
* @RequestBody 请求体
* @RequestHeader 请求头
*/
@RequestMapping("/testRequestBody03")
public String testRequestBody(HttpEntity str){
System.out.println("请求内容:" + str); // 请求体+请求头
return "success";
}
```
### 6、【ResponseEntity】既能返回响应数据,还能定制响应头、状态码
testOther.jsp
```jsp
```
AJaxTestController.jsp
```java
/**
* @ResponseBody 会将返回值的数据放在响应体中。(只要加上这个注解,返回值就相当是响应体了。)
* return的不是success.jsp页面,而是"success"字符串,另外,这个字符串可以定义为HTML格式。
*
* 除了返回String类型外,还可以返回类型:ResponseEntity 响应实体
* T:响应体中内容的类型。
* 除了能返回响应数据以外,还可以自定义响应头、响应码
* 返回响应实体时,不需要写注解【@ResponseBody】
*/
//@ResponseBody
@RequestMapping("/test01")
//public String test01(){
public ResponseEntity test01(){
// 请求体
String body = "
success
";// 自定义请求头
MultiValueMap headers = new HttpHeaders();
headers.add("Set-Cookie", "username=jack");
// 状态码(自己看源码就知道写哪些了。)
HttpStatus statusCode = HttpStatus.OK;
//return "
success
"; // 可以使用HTML格式return new ResponseEntity(body, headers, statusCode); // 返回自定义的请求体
}
```
### 7、文件下载
springmvc也支持下载,但原生的javaweb比它还好很多。。
定义响应体【ResponseEntity】给客户端即可。
```jsp
```
```java
/**
* SpringMVC的文件下载
*/
@RequestMapping("/download")
public ResponseEntity download(HttpServletRequest request) throws IOException {
// 1、得到流
// 想得到要下载文件的流,需要先得到文件的地址
// 服务器发布后,文件真实路径是在webapp项目下
// 找到文件真实路径
ServletContext context = request.getServletContext();
String realPath = context.getRealPath("scripts/jquery-1.9.1.min.js");// 这里以jQuery文件为例
FileInputStream is = new FileInputStream(realPath);
// 将流读成字节数组
// 定义字节数组大小,与文件流一样的大小【available()】,文件流有多大字节数据就有多大。
byte[] temp = new byte[is.available()];
is.read(temp);
is.close();
// 2、将要下载的流返回
MultiValueMap headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=jquery-1.9.1.min.js");
return new ResponseEntity(temp,headers,HttpStatus.OK);
}
```
### 8、【HttpMessageConverter】专门处理请求与响应信息
它是用来处理请求、响应信息的转化的,
各种类型如何变成请求、响应的输入流、输出流、java对象、json、其它数据类型等,都是它负责。
![18](图片02/18-请求信息转换.PNG)
底层就是将请求、响应信息转化为输入流、输出流了,然后抽象成一个对象,如此不管变为java对象还是json格式都方便了。
![18-2](图片02/18-2-关联.PNG)
补充
![18-3](图片02/18-3-补充.PNG)
### 9、文件上传
#### 1、原生javaweb的方式上传文件
![19](图片02/19-原生方式上传文件.PNG)
#### 2、SpringMVC的方式上传文件
利用九大组件之一:文件上传解析器(MultipartResolver)
```jsp
文件上传
1、文件上传表单准备
enctype="multipart/form-data"
2、导入fileupload包
commons-io-2.0.jar
commons-fileupload-1.2.1.jar
3、在SpringMVC配置文件中,配置文件上传解析器(九大组件之一:MultipartResolver)
4、文件上传请求处理
在处理器方法上,写一个参数:@RequestParam("headerimg") MultipartFile file
这个参数封装了当前文件的信息,可以直接保存
--%>
${msg}
用户头像:
用户名:
```
springmvc.xml
```xml
```
处理器方法
```java
@RequestMapping("/upload")
public String upload(
@RequestParam(value = "username", required = false) String username,
@RequestParam("headerimg") MultipartFile file,
Model model){
System.out.println("上传的文件信息");
System.out.println("文件项的name值:" + file.getName());
System.out.println("文件名:" + file.getOriginalFilename());
// 文件保存
try {
file.transferTo(new File("D:\\temp\\" + file.getOriginalFilename())); // 提前创建temp文件夹哦,不然就是上传失败
model.addAttribute("msg", "文件上传成功");
} catch (IOException e) {
model.addAttribute("msg", "文件上传失败"+e.getMessage());
}
return "forward:/index.jsp"; // 转发才能传递值给页面
}
```
### 10、多文件上传
```jsp
${msg}
用户头像:
用户头像02:
用户头像03:
用户头像04:
用户头像05:
用户名:
```
```java
/**
* 多文件上传 @RequestParam("headerimg") MultipartFile[] files
*
* 如果name值多个都是“headerimg”,那么就放到同一个数组中,批量上传
* 如果有其它的name值例如“header”,那应该再写一个参数。
*/
@RequestMapping("/upload")
public String upload(
@RequestParam(value = "username", required = false) String username,
@RequestParam("headerimg") MultipartFile[] files, // 多个文件
Model model){
System.out.println("上传的文件信息");
for (MultipartFile file : files) {
if (!file.isEmpty()) {
try {
file.transferTo(new File("D:\\temp\\" + file.getOriginalFilename()));
model.addAttribute("msg", "文件上传成功");
} catch (IOException e) {
model.addAttribute("msg", "文件上传失败"+e.getMessage());
}
}
}
return "forward:/index.jsp";
}
```
一键复制
编辑
Web IDE
原始数据
按行查看
历史