mvc ajax绑定数据类型,SpringMVC笔记03.md

## 十二、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

员工列表

员工的详细信息表

idlastNameemailgenderdepartmenteditdelete
${emp.id}${emp.lastName}${emp.email}${emp.gender==0?"女":"男"}${emp.department}editdelete

```

### 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());

/* 页面中建议使用绝对路径,即所有请求要加上:项目路径 */

%>

edit

```

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

@Email

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 page

pageContext.setAttribute("ctp", request.getContextPath());

%>

不刷新页面的情况下获取所有员工信息,

发请求,观察时间不变化即可验证是局部刷新。

--%>

ajax获取所有员工

$("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 page

pageContext.setAttribute("ctp", request.getContextPath());

%>

```

### 4、发送json数据给服务器

testOther.jsp

```jsp

Ajax发送json数据

$("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

原始数据

按行查看

历史

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值