前言

本文概括

  • 理解SpringMVC相关概念
  • 完成SpringMVC的入门案例
  • 学会使用PostMan工具发送请求和数据
  • 掌握SpringMVC如何接收请求、数据和响应结果
  • 掌握RESTful风格及其使用
  • 掌握异常处理器
  • 掌握拦截器

SSM学习目录

  1. Spring(一)-SSM框架(了解即可(๑・̀ㅂ・́)و✧ ) 点击我查看
  2. Spring(二)-SSM框架(了解即可ヾ (≧▽≦*) o) 点击我查看
  3. Spring(三)-SSM框架(了解即可(^▽^)) 点击我查看
  4. SpringMVC-SSM框架(~(〃∀`)ノ) 点击我查看(当前位置)

初识SpringMVC

SpringMVC是一种基于Java的Web应用程序开发框架它采用了模型 - 视图 - 控制器(MVC)的架构模式,用于构建灵活、可扩展和高效的 Web 应用程序。
MVC架构 :MVC 是一种软件架构思想,将软件按模型(Model)、视图(View)、控制器(Controller)划分。在 SpringMVC 中:
Model(模型层):对应 Java 中的 JavaBean ,处理数据及业务逻辑,通常由 Service 层和 Dao 层支持 ,比如处理订单数据的计算、用户信息的存储等。
View(视图层):像 html、jsp 等页面,负责与用户交互并展示数据,比如将商品信息展示给用户。
Controller(控制层):由普通 Java 类(POJO)担任,需用 @Controller 注解标识为控制层组件并交给 Spring 的 IOC 容器管理 ,它接收请求、调用 Model 处理请求,再根据结果找到对应 View,例如处理用户登录请求。

SpringMVC的主要任务在于:

  • controller如何接收请求和数据
  • 如何将请求和数据转发给业务层
  • 如何将响应数据转换成json发回到前端

SpringMVC的入门案例

因为SpringMVC是一个Web框架,将来是要替换Servlet,所以先来回顾下以前Servlet是如何进行开发的?
1.创建web工程(Maven结构)
2.设置tomcat服务器,加载web工程(tomcat插件)
3.导入坐标(Servlet)
4.定义处理请求的功能类(UserServlet)
5.设置请求映射(配置映射关系)
SpringMVC的制作过程和上述流程几乎是一致的,具体的实现流程是什么?
1.创建web工程(Maven结构)
2.设置tomcat服务器,加载web工程(tomcat插件)
3.导入坐标(SpringMVC+Servlet)
4.定义处理请求的功能类(UserController)
5.设置请求映射(配置映射关系)
6.将SpringMVC设定加载到Tomcat容器中

1.创建web工程(Maven结构)
2.设置tomcat服务器,加载web工程(tomcat插件)
3.导入jar包
将pom.xml中多余的内容删除掉,再添加SpringMVC需要的依赖

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>springmvc_01_quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>

说明:servlet的坐标为什么需要添加<scope>provided</scope>?

  • scope是maven中jar包依赖作用范围的描述,
  • 如果不设置默认是compile在在编译、运行、测试时均有效
  • 如果运行有效的话就会和tomcat中的servlet-api包发生冲突,导致启动报错
  • provided代表的是该包只在编译和测试的时候用,运行的时候无效直接使用tomcat中的,就避免冲突

步骤4:创建配置类

1
2
3
4
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}

步骤5:创建Controller类

1
2
3
4
5
6
7
8
@Controller
public class UserController {

@RequestMapping("/save")
public void save(){
System.out.println("user save ...");
}
}

步骤6:使用配置类替换web.xml
将web.xml删除,换成ServletContainersInitConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc配置类
protected WebApplicationContext createServletApplicationContext() {
//初始化WebApplicationContext对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载指定配置类
ctx.register(SpringMvcConfig.class);
return ctx;
}

//设置由springmvc控制器处理的请求映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}

//加载spring配置类
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}

步骤7:配置Tomcat环境
步骤8:启动运行项目
步骤9:浏览器访问
浏览器输入http://localhost/save进行访问,会报错误,页面报错的原因是后台没有指定返回的页面,目前只需要关注控制台看user save ...有没有被执行即可。
步骤10:修改Controller返回值解决上述问题
前面我们说过现在主要的是前端发送异步请求,后台响应json数据,所以接下来我们把Controller类的save方法进行修改

1
2
3
4
5
6
7
8
9
@Controller
public class UserController {

@RequestMapping("/save")
public String save(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}
}

再次重启tomcat服务器,然后重新通过浏览器测试访问,会发现还是会报错,这次的错是404
出错的原因是,如果方法直接返回字符串,springmvc会把字符串当成页面的名称在项目中进行查找返回,因为不存在对应返回值名称的页面,所以会报404错误,找不到资源。
而我们其实是想要直接返回的是json数据,具体如何修改呢?
步骤11:设置返回数据为json格式
1
2
3
4
5
6
7
8
9
10
11
@Controller
public class UserController {

@RequestMapping("/save")
//设置返回数据为json格式
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}
}

再次重启tomcat服务器,然后重新通过浏览器测试访问,就能看到返回的结果数据

注意事项

  • SpringMVC是基于Spring的,在pom.xml只导入了spring-webmvcjar包的原因是它会自动依赖spring相关坐标
  • AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类
  • AbstractDispatcherServletInitializer提供了三个接口方法供用户实现
    • createServletApplicationContext方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
    • getServletMappings方法,设定SpringMVC对应的请求映射路径,即SpringMVC拦截哪些请求
    • createRootApplicationContext方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式和createServletApplicationContext相同。
    • createServletApplicationContext用来加载SpringMVC环境
    • createRootApplicationContext用来加载Spring环境

注解知识点小结

知识点1:@Controller

名称 @Controller
类型 类注解
位置 SpringMVC控制器类定义上方
作用 设定SpringMVC的核心控制器bean

知识点2:@RequestMapping

名称 @RequestMapping
类型 类注解或方法注解
位置 SpringMVC控制器类或方法定义上方
作用 设置当前控制器方法请求访问路径
相关属性 value(默认),请求访问路径

知识点3:@ResponseBody

名称 @ResponseBody
类型 类注解或方法注解
位置 SpringMVC控制器类或方法定义上方
作用 设置当前控制器方法响应内容为当前返回值,无需解析

工作流程解析

为了更好的使用SpringMVC,我们将SpringMVC的使用过程总共分两个阶段来分析,分别是启动服务器初始化过程单次请求过程
启动服务器初始化过程

  1. 服务器启动,执行ServletContainersInitConfig类,初始化web容器

    • 功能类似于以前的web.xml
  2. 执行createServletApplicationContext方法,创建了WebApplicationContext对象

    • 该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
  3. 加载SpringMvcConfig配置类

  4. 执行@ComponentScan加载对应的bean

    • 扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Controller
    public class UserController {
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
    System.out.println("user save...");
    return "{'info':'springmvc'}";
    }
    }
    • 此时就建立了 /save 和 save方法的对应关系
  6. 执行getServletMappings方法,设定SpringMVC拦截请求的路径规则

    1
    2
    3
    protected String[] getServletMappings() {
    return new String[]{"/"};
    }
    • /代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求

单次请求过程

  1. 发送请求http://localhost/save
  2. web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理
  3. 解析请求路径/save
  4. 由/save匹配执行对应的方法save()
    • 上面的第五步已经将请求路径和方法建立了对应关系,通过/save就能找到对应的save方法
  5. 执行save()
  6. 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方

bean的加载控制

入门案例的内容已经做完了,在入门案例中我们创建过一个SpringMvcConfig的配置类,再回想前面咱们学习Spring的时候也创建过一个配置类SpringConfig。这两个配置类都需要加载资源,那么它们分别都需要加载哪些内容?

  • SpringConfig配置类:加载Dao和Service
  • SpringMvcConfig配置类:加载Controller

在SpringMVC中,我们可以通过配置类来控制bean的加载范围,在配置类中使用@ComponentScan注解来指定需要扫描的包路径,从而加载指定包及其子包下的所有类上的注解,例如@Controller注解。

1
2
3
4
5
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
//...
}

在上述代码中EMAIL在上述代码中,@ComponentScan("com.itheima.controller")指定了需要扫描的包路径为com.itheima.controller,即只加载该包及其子包下的所有类上的注解,例如@Controller注解。这样,在SpringMVC中就只会加载Controller类上的注解,而不会加载其他类上的注解,从而实现了bean的加载范围控制。

而在Spring配置类中,我们可以采用一下三种方法来设置bean的加载控制

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
//方式一:修改Spring配置类,设定扫描范围为精准范围。
@Connfiguration
@ComponentScan(basePackages = {"com.itheima.dao","com.itheima.service"})
public class SpringConfig {
}
//方式二:修改Spring配置类,设定扫描范围为com.itheima,排除掉controller包中的bean
@Connfiguration
@ComponentScan(value="com.itheima",
excludeFilters=@ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
)
)
public class SpringConfig {
}
// excludeFilters属性:设置扫描加载bean时,排除的过滤规则
// type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除
// ANNOTATION:按照注解排除
// ASSIGNABLE_TYPE:按照指定的类型过滤
// ASPECTJ:按照Aspectj表达式排除,基本上不会用
// REGEX:按照正则表达式排除
// CUSTOM:按照自定义规则排除
// 大家只需要知道第一种ANNOTATION即可
// classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean

// 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]

注解知识点小结

知识点1:@ComponentScan

名称 @ComponentScan
类型 类注解
位置 类定义上方
作用 设置spring配置类扫描路径,用于加载使用注解格式定义的bean
相关属性 excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes)
includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes)

请求与响应

请求参数

请求路径设置好后,只要确保页面发送请求地址和后台Controller类中配置的路径一致,就可以接收到前端的请求,接收到请求后,如何接收页面传递的参数?

关于请求参数的传递与接收是和请求方式有关系的,目前比较常见的两种请求方式为:

  • GET
  • POST

参数传递

GET发送单个参数:
发送请求与参数:

1
http://localhost/commonParam?name=itcast

接收参数:
1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {

@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name){
System.out.println("普通参数传递 name ==> "+name);
return "{'module':'commonParam'}";
}
}

GET发送多个参数
发送请求与参数:

1
http://localhost/commonParam?name=itcast&age=15

接收参数:
1
2
3
4
5
6
7
8
9
10
11
@Controller
public class UserController {

@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name,int age){
System.out.println("普通参数传递 name ==> "+name);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'commonParam'}";
}
}

POST发送参数
发送请求与参数:

1
http://localhost/commonParam?name=itcast&age=15

接收参数:
1
2
3
4
5
6
7
8
9
10
11
@Controller
public class UserController {

@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name,int age){
System.out.println("普通参数传递 name ==> "+name);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'commonParam'}";
}
}

五种类型参数传递

前面我们已经能够使用GET或POST来发送请求和数据,所携带的数据都是比较简单的数据,接下来在这个基础上,我们来研究一些比较复杂的参数传递,常见的参数种类有:

  • 普通参数
  • POJO类型参数
  • 嵌套POJO类型参数
  • 数组类型参数
  • 集合类型参数

这些参数如何发送,后台改如何接收?我们一个个来学习。

普通参数
普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。

1
http://localhost:8080/springmvc/test1?name=zhangsan&age=18

接收参数:
1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {
@RequestMapping("/test1")
@ResponseBody
public String test1(String name, int age){
System.out.println("普通参数传递 name ==> "+name);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'test1'}";
}
}

如果形参与地址参数名不一致该如何解决?
发送请求与参数:
1
http://localhost/commonParamDifferentName?name=张三&age=18

接收参数:
1
2
3
4
5
6
7
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestPaam("name") String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}

使用@RequestParam注解来指定形参变量与地址参数名的对应关系。

POJO类型参数
POJO类型参数:url地址传参,地址参数名与POJO对象属性名相同,定义POJO对象即可接收参数。
发送请求与参数:

1
2
3
4
5
public class User {
private String name;
private int age;
//setter...getter...略
}

发送请求和参数:
1
http://localhost:8080/user/addUser?name=张三&age=20

接收参数:
1
2
3
4
5
6
7
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}

嵌套POJO类型参数
嵌套POJO类型参数:url地址传参,地址参数名与POJO对象属性名相同,定义POJO对象即可接收参数。

1
2
3
4
5
6
7
8
9
10
11
public class Address {
private String province;
private String city;
//setter...getter...略
}
public class User {
private String name;
private int age;
private Address address;
//setter...getter...略
}

发送请求和参数:
1
http://localhost:8080/user/addUser?name=张三&age=18&address.province=北京&address.city=北京

接收参数:
1
2
3
4
5
6
7
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}

数组类型参数
数组类型参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。
发送请求和参数:

1
http://localhost:8080/user/arrayParam?ids=1&ids=2&ids=3

接收参数:
1
2
3
4
5
6
7
//数组参数:请求参数名与形参变量名相同,形参变量为数组类型即可接收参数
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(int[] ids){
System.out.println("数组参数传递 ids ==> "+ Arrays.toString(ids));
return "{'module':'array param'}";
}

集合类型参数
集合类型参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。
发送请求和参数:

1
http://localhost:8080/springmvc/collectionParam?books=book1&books=book2&books=book3

接收参数:
1
2
3
4
5
6
7
//集合参数:请求参数名与形参变量名相同,形参变量为集合类型即可接收参数
@RequestMapping("/collectionParam")
@ResponseBody
public String collectionParam(List<String> books){
System.out.println("集合参数传递 books ==> "+books);
return "{'module':'collection param'}";
}

注解知识点小结

名称 @RequestParam
类型 形参注解
位置 SpringMVC控制器方法形参定义前面
作用 绑定请求参数与处理器方法形参间的关系
相关参数 required:是否为必传参数
defaultValue:参数默认值

JSON数据传输参数

前面我们说过,现在比较流行的开发方式为异步调用。前后台以异步方式进行交换,传输的数据使用的是==JSON==,所以前端如果发送的是JSON数据,后端该如何接收?

对于JSON数据类型,我们常见的有三种:

  • json普通数组([“value1”,”value2”,”value3”,…])
  • json对象({key1:value1,key2:value2,…})
  • json对象数组([{key1:value1,…},{key2:value2,…}])

对于上述数据,前端如何发送,后端如何接收?

JSON普通数组
步骤1:pom.xml添加依赖
SpringMVC默认使用的是jackson来处理json的转换,所以需要在pom.xml添加jackson依赖

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>

步骤2:发送JSON数据
1
URL_ADDRESShttp://localhost:8080/user/saveUser?hobbies=["篮球","足球","乒乓球"]

步骤3:开启SpringMVC注解支持
1
2
3
4
5
6
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}

步骤4:参数前添加@RequestBody注解
1
2
3
4
5
6
7
//使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}

步骤5:启动运行程序

JSON对象数据
我们会发现,只需要关注请求和数据如何发送?后端数据如何接收?
请求和数据的发送:

1
2
3
4
{
"name":"itcast",
"age":15
}

后端接收数据:
1
2
3
4
5
6
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
System.out.println("pojo(json)参数传递 user ==> "+user);
return "{'module':'pojo for json param'}";
}

启动程序访问测试
说明:
address为null的原因是前端没有传递数据给后端。
如果想要address也有数据,我们需求修改前端传递的数据内容:
1
2
3
4
5
6
7
8
{
"name":"itcast",
"age":15,
"address":{
"province":"beijing",
"city":"beijing"
}
}

再次发送请求,就能看到address中的数据

JSON对象数组
集合中保存多个POJO该如何实现?
请求和数据的发送:

1
2
3
4
[
{"name":"itcast","age":15},
{"name":"itheima","age":12}
]

后端接收数据:
1
2
3
4
5
6
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> list){
System.out.println("list pojo(json)参数传递 list ==> "+list);
return "{'module':'list pojo for json param'}";
}

启动程序访问测试

小结

SpringMVC接收JSON数据的实现步骤为:
(1)导入jackson包
(2)使用PostMan发送JSON数据
(3)开启SpringMVC注解驱动,在配置类上添加@EnableWebMvc注解
(4)Controller方法的参数前添加@RequestBody注解

注解知识点小结

知识点1:@EnableWebMvc

名称 @EnableWebMvc
类型 ==配置类注解==
位置 SpringMVC配置类定义上方
作用 开启SpringMVC多项辅助功能

知识点2:@RequestBody

名称 @RequestBody
类型 ==形参注解==
位置 SpringMVC控制器方法形参定义前面
作用 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次

知识点3:@ResponseBody

名称 @ResponseBody
类型 ==方法\类注解==
位置 SpringMVC控制器方法定义上方和控制类上
作用 设置当前控制器返回值作为响应体,
写在类上,该类的所有方法都有该注解功能

说明:

  • 该注解可以写在类上或者方法上
  • 写在类上就是该类下的所有方法都有@ReponseBody功能
  • 当方法上有@ReponseBody注解后
    • 方法的返回值为字符串,会将其作为文本内容直接响应给前端
    • 方法的返回值为对象,会将对象转换成JSON响应给前端

@RequestBody与@RequestParam区别

  • 区别

    • @RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】
    • @RequestBody用于接收json数据【application/json】
  • 应用

    • 后期开发中,发送json格式数据为主,@RequestBody应用较广
    • 如果发送非json格式数据,选用@RequestParam接收请求参数

日期类型参数传递

日期类型比较特殊,因为对于日期的格式有N多中输入方式,比如:

  • 2088-08-18
  • 2088/08/18
  • 08/18/2088
  • ……

针对这么多日期格式,SpringMVC该如何接收,它能很好的处理日期类型数据么?
我们需要使用@DateTimeFormat注解来指定日期的格式,如下所示:

1
2
3
4
5
6
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam{(@DateTimeFormat(pattern="yyyy-MM-dd") Date date)
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
return "{'module':'data param'}";
}

如果有两个不同形式的日期格式,我们可以按如下
1
2
3
4
5
6
7
8
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam{(@DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2)
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
return "{'module':'data param'}";
}

注解知识点小结

知识点1:@DateTimeFormat

名称 @DateTimeFormat
类型 ==形参注解==
位置 SpringMVC控制器方法形参前面
作用 设定日期时间型数据格式
相关属性 pattern:指定日期时间格式字符串

Rest风格

对于Rest风格,我们需要学习的内容包括:

  • REST简介
  • REST入门案例
  • REST快速开发

REST简介

REST(REpresentational State Transfer)是一种软件架构风格,它是基于HTTP协议的一种设计风格。REST是一种基于资源的设计风格,它的核心思想是将资源作为网络中的实体,通过HTTP协议进行交互。
当我们想表示一个网络资源的时候,可以使用两种方式:

  • 传统风格资源描述形式
    • http://localhost/user/getById?id=1 查询id为1的用户信息
    • http://localhost/user/saveUser 保存用户信息
  • REST风格描述形式
    • http://localhost/user/1
    • http://localhost/user

按照不同的请求方式代表不同的操作类型。

  • 发送GET请求是用来做查询
  • 发送POST请求是用来做新增
  • 发送PUT请求是用来做修改
  • 发送DELETE请求是用来做删除

清楚了什么是REST风格后,我们后期会经常提到一个概念叫RESTful,那什么又是RESTful呢?

  • 根据REST风格对资源进行访问称为==RESTful==。

后期我们在进行开发的过程中,大多是都是遵从REST风格来访问我们的后台服务,所以可以说咱们以后都是基于RESTful来进行开发的。

REST入门案例

环境准备

点击查看代码

创建一个Web的Maven项目
pom.xml添加Spring依赖

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
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itheima</groupId>
<artifactId>springmvc_06_rest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>

创建对应的配置类

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
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}

protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}

protected String[] getServletMappings() {
return new String[]{"/"};
}

//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}

@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}


编写模型类User和Book
1
2
3
4
5
6
7
8
9
10
11
public class User {
private String name;
private int age;
//getter...setter...toString省略
}

public class Book {
private String name;
private double price;
//getter...setter...toString省略
}

编写UserController和BookController

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
71
72
73
74
75
76
77
78
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestBody User user) {
System.out.println("user save..."+user);
return "{'module':'user save'}";
}

@RequestMapping("/delete")
@ResponseBody
public String delete(Integer id) {
System.out.println("user delete..." + id);
return "{'module':'user delete'}";
}

@RequestMapping("/update")
@ResponseBody
public String update(@RequestBody User user) {
System.out.println("user update..." + user);
return "{'module':'user update'}";
}

@RequestMapping("/getById")
@ResponseBody
public String getById(Integer id) {
System.out.println("user getById..." + id);
return "{'module':'user getById'}";
}

@RequestMapping("/findAll")
@ResponseBody
public String getAll() {
System.out.println("user getAll...");
return "{'module':'user getAll'}";
}
}


@Controller
public class BookController {

@RequestMapping(value = "/books",method = RequestMethod.POST)
@ResponseBody
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}

@RequestMapping(value = "/books/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}

@RequestMapping(value = "/books",method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}

@RequestMapping(value = "/books/{id}",method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}

@RequestMapping(value = "/books",method = RequestMethod.GET)
@ResponseBody
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}

}

案例:REST风格描述形式

新增

1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {
//设置当前请求方法为POST,表示REST风格中的添加操作
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save() {
System.out.println("user save...");
return "{'module':'user save'}";
}
}

删除
1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {
//设置当前请求方法为DELETE,表示REST风格中的删除操作
@RequestMapping(value = "/users",method = RequestMethod.DELETE)
@ResponseBody
public String delete(Integer id) {
System.out.println("user delete..." + id);
return "{'module':'user delete'}";
}
}

修改
1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {
//设置当前请求方法为DELETE,表示REST风格中的删除操作
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id) {
System.out.println("user delete..." + id);
return "{'module':'user delete'}";
}
}

如果方法形参名称和路径{}中值不一致,还有多个参数的传递我们应该怎么办
我们可以做如下修改:
1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {
//设置当前请求方法为DELETE,表示REST风格中的删除操作
@RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable("id") Integer userid,@PathVariable String name) {
System.out.println("user delete..." + id+","+name);
return "{'module':'user delete'}";
}
}

注解知识点小结

名称 @PathVariable
类型 ==形参注解==
位置 SpringMVC控制器方法形参定义前面
作用 绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应

关于接收参数,我们学过三个注解@RequestBody@RequestParam@PathVariable,这三个注解之间的区别和应用分别是什么?

  • 区别
    • @RequestParam用于接收url地址传参或表单传参
    • @RequestBody用于接收json数据
    • @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
  • 应用
    • 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
    • 如果发送非json格式数据,选用@RequestParam接收请求参数
    • 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值

RESTful快速开发

对于上述的案例,我们可以发现,对于RESTful风格的开发,我们在编写代码时,需要写很多的重复代码,为了简化开发,我们可以采用RESTful快速开发。
我们可以使用@RestController注解来简化开发,@RestController@Controller@ResponseBody的组合,我们将@RequestMapping注解写在类上,用来定义所有方法共同的访问路径。

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
@RestController //@Controller + ReponseBody
@RequestMapping("/books")
public class BookController {

//@RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}

//@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}

//@RequestMapping(method = RequestMethod.PUT)
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}

//@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}

//@RequestMapping(method = RequestMethod.GET)
@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}

}

注解知识点小结

知识点1:@RestController

名称 @RestController
类型 ==类注解==
位置 基于SpringMVC的RESTful开发控制器类定义上方
作用 设置当前控制器类为RESTful风格,<br/>等同于@Controller与@ResponseBody两个注解组合功能

知识点2:@GetMapping @PostMapping @PutMapping @DeleteMapping

名称 @GetMapping @PostMapping @PutMapping @DeleteMapping
类型 ==方法注解==
位置 基于SpringMVC的RESTful开发控制器方法定义上方
作用 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,<br/>例如@GetMapping对应GET请求
相关属性 value(默认):请求访问路径

统一异常处理

问题描述

在讲解这一部分知识点之前,我们先来演示个效果,写一个BookController类的getById方法

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
//手动添加一个错误信息
if(id==1){
int i = 1/0;
}
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code,book,msg);
}

重新启动运行项目,使用PostMan发送请求,当传入的id为1,则会抛出异常.
前端接收到这个信息后和之前我们约定的格式不一致,这个问题该如何解决?

在解决问题之前,我们先来看下异常的种类及出现异常的原因:

  • 框架内部抛出的异常:因使用不合规导致
  • 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
  • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
  • 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
  • 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)

看完上面这些出现异常的位置,你会发现,在我们开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。所以我们就得将异常进行处理。

异常处理器的使用

步骤1:创建异常处理器类

1
2
3
4
5
6
7
8
9
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public void doException(Exception ex){
System.out.println("嘿嘿,异常你哪里跑!")
}
}

步骤2:让程序抛出异常
1
2
3
4
5
6
7
8
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
int i = 1/0;
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code,book,msg);
}

步骤3:运行程序,测试效果,查看控制台,看到异常已经被拦截并执行了doException方法。
异常处理器类返回结果给前端
1
2
3
4
5
6
7
8
9
10
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
System.out.println("嘿嘿,异常你哪里跑!")
return new Result(666,null,"嘿嘿,异常你哪里跑!");
}
}

前端看到的就会是json格式的数据

注解知识点小结

知识点1:@RestControllerAdvice

名称 @RestControllerAdvice
类型 ==类注解==
位置 Rest风格开发的控制器增强类定义上方
作用 为Rest风格开发的控制器类做增强

知识点2:@ExceptionHandler

名称 @ExceptionHandler
类型 ==方法注解==
位置 专用于异常处理的控制器方法上方
作用 设置指定异常的处理方案,功能等同于控制器方法,
出现异常后终止原始控制器执行,并转入当前方法执行

异常解决方案

  • 业务异常(BusinessException)
    • 发送对应消息传递给用户,提醒规范操作
      • 大家常见的就是提示用户名已存在或密码格式不正确等
  • 系统异常(SystemException)
    • 发送固定消息传递给用户,安抚用户
      • 系统繁忙,请稍后再试
      • 系统正在维护升级,请稍后再试
      • 系统出问题,请联系系统管理员等
    • 发送特定消息给运维人员,提醒维护
      • 可以发送短信、邮箱或者是公司内部通信软件
    • 记录日志
      • 发消息和记录日志对用户来说是不可见的,属于后台程序
  • 其他异常(Exception)
    • 发送固定消息传递给用户,安抚用户
    • 发送特定消息给编程人员,提醒维护(纳入预期范围内)
      • 一般是程序没有考虑全,比如未做非空校验等
    • 记录日志

异常解决方案的具体实现

思路:

1.先通过自定义异常,完成BusinessException和SystemException的定义

2.将其他异常包装成自定义异常类型

3.在异常处理器类中对不同的异常进行处理

步骤1:自定义异常类

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
//自定义异常处理器,用于封装异常信息,对异常进行分类
public class SystemException extends RuntimeException{
private Integer code;

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public SystemException(Integer code, String message) {
super(message);
this.code = code;
}

public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}

}

//自定义异常处理器,用于封装异常信息,对异常进行分类
public class BusinessException extends RuntimeException{
private Integer code;

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}

public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}

}


说明:

  • 让自定义异常类继承RuntimeException的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了
  • 自定义异常类中添加code属性的原因是为了更好的区分异常是来自哪个业务的

步骤2:将其他异常包成自定义异常

假如在BookServiceImpl的getById方法抛异常了,该如何来包装呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
public Book getById(Integer id) {
//模拟业务异常,包装成自定义异常
if(id == 1){
throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的耐性!");
}
//模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
try{
int i = 1/0;
}catch (Exception e){
throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
}
return bookDao.getById(id);
}

具体的包装方式有:

  • 方式一:try{}catch(){}在catch中重新throw我们自定义异常即可。
  • 方式二:直接throw自定义异常即可

上面为了使code看着更专业些,我们在Code类中再新增需要的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//状态码
public class Code {
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer GET_OK = 20041;

public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_ERR = 20040;
public static final Integer SYSTEM_ERR = 50001;
public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
public static final Integer SYSTEM_UNKNOW_ERR = 59999;

public static final Integer BUSINESS_ERR = 60002;
}

步骤3:处理器类中处理自定义异常

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
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//@ExceptionHandler用于设置当前处理器类对应的异常类型
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(ex.getCode(),null,ex.getMessage());
}

@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex){
return new Result(ex.getCode(),null,ex.getMessage());
}

//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
}
}

SpringMVC拦截器

拦截器的概念

拦截器是SpringMVC框架中的一个组件,用于拦截和处理请求和响应。它可以在请求到达控制器之前或之后执行一些操作,例如验证用户身份、记录日志、处理异常等。

拦截器的作用

拦截器可以实现以下功能:

  1. 身份验证:拦截器可以检查用户的身份是否合法,例如检查用户是否登录、是否具有访问权限等。
  2. 日志记录:拦截器可以记录请求的日志信息,例如记录请求的URL、请求参数、响应结果等。
  3. 异常处理:拦截器可以捕获并处理异常,例如记录异常信息、返回错误页面等。
  4. 数据转换:拦截器可以对请求和响应的数据进行转换,例如将请求参数转换为对象、将对象转换为响应数据等。
  5. 数据校验:拦截器可以对请求数据进行校验,例如检查请求参数是否符合要求等。
  6. 数据过滤:拦截器可以对请求数据进行过滤,例如过滤掉一些敏感信息等。

拦截器的实现

环境准备

点击查看代码

pom.xml添加SSM整合所需jar包

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
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itheima</groupId>
<artifactId>springmvc_12_interceptor</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

创建对应的配置类

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
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}

protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}

protected String[] getServletMappings() {
return new String[]{"/"};
}

//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}

@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
public class SpringMvcConfig{

}

创建模型类Book

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
public class Book {
private String name;
private double price;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return "Book{" +
"书名='" + name + '\'' +
", 价格=" + price +
'}';
}
}

编写Controller

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
@RestController
@RequestMapping("/books")
public class BookController {

@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}

@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}

@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..."+book);
return "{'module':'book update'}";
}

@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("book getById..."+id);
return "{'module':'book getById'}";
}

@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}

拦截器的实现步骤

步骤1:创建拦截器类

让类实现HandlerInterceptor接口,重写接口中的三个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
@Override
//原始方法调用前执行的内容
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return true;
}

@Override
//原始方法调用后执行的内容
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}

@Override
//原始方法调用完成后执行的内容
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}

注意:拦截器类要被SpringMVC容器扫描到。

步骤2:配置拦截器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;

@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}

@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books" );
}
}

步骤3:SpringMVC添加SpringMvcSupport包扫描

1
2
3
4
5
6
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig{

}

步骤5:修改拦截器拦截规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;

@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}

@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );
}
}

步骤6:简化SpringMvcSupport的编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置多拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
}
}

此后咱们就不用再写SpringMvcSupport类了。

拦截器参数

前置处理方法

原始方法之前运行preHandle

1
2
3
4
5
6
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
  • request:请求对象
  • response:响应对象
  • handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装

使用request对象可以获取请求数据中的内容,如获取请求头的Content-Type

1
2
3
4
5
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contentType = request.getHeader("Content-Type");
System.out.println("preHandle..."+contentType);
return true;
}

使用handler参数,可以获取方法的相关信息

1
2
3
4
5
6
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod hm = (HandlerMethod)handler;
String methodName = hm.getMethod().getName();//可以获取方法的名称
System.out.println("preHandle..."+methodName);
return true;
}

后置处理方法

原始方法运行后运行,如果原始方法被拦截,则不执行

1
2
3
4
5
6
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}

前三个参数和上面的是一致的。

modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整

因为咱们现在都是返回json数据,所以该参数的使用率不高。

完成处理方法

拦截器最后执行的方法,无论原始方法是否执行

1
2
3
4
5
6
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion");
}

前三个参数与上面的是一致的。

ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理

因为我们现在已经有全局异常处理器类,所以该参数的使用率也不高。

这三个方法中,最常用的是==preHandle==,在这个方法中可以通过返回值来决定是否要进行放行,我们可以把业务逻辑放在该方法中,如果满足业务则返回true放行,不满足则返回false拦截。

拦截器链配置

目前,我们在项目中只添加了一个拦截器,如果有多个,该如何配置?配置多个后,执行顺序是什么?

配置多个拦截器

步骤1:创建拦截器类

实现接口,并重写接口中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...222");
return false;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...222");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...222");
}
}

步骤2:配置拦截器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Autowired
private ProjectInterceptor2 projectInterceptor2;

@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置多拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");
}
}

拦截器执行的顺序是和配置顺序有关。就和前面所提到的运维人员进入机房的案例,先进后出。

  • 当配置多个拦截器时,形成拦截器链
  • 拦截器链的运行顺序参照拦截器添加顺序为准
  • 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
  • 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作

preHandle:与配置顺序相同,必定运行

postHandle:与配置顺序相反,可能不运行

afterCompletion:与配置顺序相反,可能不运行。