前言
本文概括
- 理解SpringMVC相关概念
- 完成SpringMVC的入门案例
- 学会使用PostMan工具发送请求和数据
- 掌握SpringMVC如何接收请求、数据和响应结果
- 掌握RESTful风格及其使用
- 掌握异常处理器
- 掌握拦截器
SSM学习目录
- Spring(一)-SSM框架(了解即可(๑・̀ㅂ・́)و✧ ) 点击我查看
- Spring(二)-SSM框架(了解即可ヾ (≧▽≦*) o) 点击我查看
- Spring(三)-SSM框架(了解即可(^▽^)) 点击我查看
- 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
<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
public class SpringMvcConfig {
}
步骤5:创建Controller类1
2
3
4
5
6
7
8
public class UserController {
public void save(){
System.out.println("user save ...");
}
}
步骤6:使用配置类替换web.xml
将web.xml删除,换成ServletContainersInitConfig1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public 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
public class UserController {
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
public class UserController {
//设置返回数据为json格式
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的使用过程总共分两个阶段来分析,分别是启动服务器初始化过程和单次请求过程
启动服务器初始化过程
服务器启动,执行ServletContainersInitConfig类,初始化web容器
- 功能类似于以前的web.xml
执行createServletApplicationContext方法,创建了WebApplicationContext对象
- 该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
加载SpringMvcConfig配置类
执行@ComponentScan加载对应的bean
- 扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
加载UserController,每个@RequestMapping的名称对应一个具体的方法
1
2
3
4
5
6
7
8
9
public class UserController {
public String save(){
System.out.println("user save...");
return "{'info':'springmvc'}";
}
}- 此时就建立了
/save和 save方法的对应关系
- 此时就建立了
执行getServletMappings方法,设定SpringMVC拦截请求的路径规则
1
2
3protected String[] getServletMappings() {
return new String[]{"/"};
}/代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求
单次请求过程
- 发送请求
http://localhost/save - web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理
- 解析请求路径/save
- 由/save匹配执行对应的方法save()
- 上面的第五步已经将请求路径和方法建立了对应关系,通过/save就能找到对应的save方法
- 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方
bean的加载控制
入门案例的内容已经做完了,在入门案例中我们创建过一个SpringMvcConfig的配置类,再回想前面咱们学习Spring的时候也创建过一个配置类SpringConfig。这两个配置类都需要加载资源,那么它们分别都需要加载哪些内容?
- SpringConfig配置类:加载Dao和Service
- SpringMvcConfig配置类:加载Controller
在SpringMVC中,我们可以通过配置类来控制bean的加载范围,在配置类中使用@ComponentScan注解来指定需要扫描的包路径,从而加载指定包及其子包下的所有类上的注解,例如@Controller注解。1
2
3
4
5
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配置类,设定扫描范围为精准范围。
public class SpringConfig {
}
//方式二:修改Spring配置类,设定扫描范围为com.itheima,排除掉controller包中的bean
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
public class UserController {
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
public class UserController {
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
public class UserController {
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
public class UserController {
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
public String commonParamDifferentName( 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
5public 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参数:请求参数与形参对象中的属性对应即可完成参数传递
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
11public 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参数:请求参数与形参对象中的属性对应即可完成参数传递
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//数组参数:请求参数名与形参变量名相同,形参变量为数组类型即可接收参数
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//集合参数:请求参数名与形参变量名相同,形参变量为集合类型即可接收参数
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
//开启json数据类型自动转换
public class SpringMvcConfig {
}
步骤4:参数前添加@RequestBody注解1
2
3
4
5
6
7//使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
public String listParamForJson( 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
public String pojoParamForJson( 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
public String listPojoParamForJson( 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
public String dataParam{( Date date)
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
return "{'module':'data param'}";
}
如果有两个不同形式的日期格式,我们可以按如下1
2
3
4
5
6
7
8
public String dataParam{( Date date1,
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/1http://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
<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
29public 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[]{"/"};
}
//乱码处理
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
//开启json数据类型自动转换
public class SpringMvcConfig {
}
编写模型类User和Book1
2
3
4
5
6
7
8
9
10
11public class User {
private String name;
private int age;
//getter...setter...toString省略
}
public class Book {
private String name;
private double price;
//getter...setter...toString省略
}
编写UserController和BookController1
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
public class UserController {
public String save( User user) {
System.out.println("user save..."+user);
return "{'module':'user save'}";
}
public String delete(Integer id) {
System.out.println("user delete..." + id);
return "{'module':'user delete'}";
}
public String update( User user) {
System.out.println("user update..." + user);
return "{'module':'user update'}";
}
public String getById(Integer id) {
System.out.println("user getById..." + id);
return "{'module':'user getById'}";
}
public String getAll() {
System.out.println("user getAll...");
return "{'module':'user getAll'}";
}
}
public class BookController {
public String save( Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
public String delete( Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
public String update( Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
public String getById( Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
案例:REST风格描述形式
新增1
2
3
4
5
6
7
8
9
10
public class UserController {
//设置当前请求方法为POST,表示REST风格中的添加操作
public String save() {
System.out.println("user save...");
return "{'module':'user save'}";
}
}
删除1
2
3
4
5
6
7
8
9
10
public class UserController {
//设置当前请求方法为DELETE,表示REST风格中的删除操作
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
public class UserController {
//设置当前请求方法为DELETE,表示REST风格中的删除操作
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
public class UserController {
//设置当前请求方法为DELETE,表示REST风格中的删除操作
public String delete( Integer userid, 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 //@Controller + ReponseBody
public class BookController {
//@RequestMapping(method = RequestMethod.POST)
public String save( Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
//@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
public String delete( Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
//@RequestMapping(method = RequestMethod.PUT)
public String update( Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
//@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public String getById( Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
//@RequestMapping(method = RequestMethod.GET)
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
public Result getById( 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风格对应的异常处理器
public class ProjectExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
public void doException(Exception ex){
System.out.println("嘿嘿,异常你哪里跑!")
}
}
步骤2:让程序抛出异常1
2
3
4
5
6
7
8
public Result getById( 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风格对应的异常处理器
public class ProjectExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
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 | //自定义异常处理器,用于封装异常信息,对异常进行分类 |
说明:
- 让自定义异常类继承
RuntimeException的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了 - 自定义异常类中添加
code属性的原因是为了更好的区分异常是来自哪个业务的
步骤2:将其他异常包成自定义异常
假如在BookServiceImpl的getById方法抛异常了,该如何来包装呢?
1 | public Book getById(Integer id) { |
具体的包装方式有:
- 方式一:
try{}catch(){}在catch中重新throw我们自定义异常即可。 - 方式二:直接throw自定义异常即可
上面为了使code看着更专业些,我们在Code类中再新增需要的属性
1 | //状态码 |
步骤3:处理器类中处理自定义异常
1 | //@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器 |
SpringMVC拦截器
拦截器的概念
拦截器是SpringMVC框架中的一个组件,用于拦截和处理请求和响应。它可以在请求到达控制器之前或之后执行一些操作,例如验证用户身份、记录日志、处理异常等。
拦截器的作用
拦截器可以实现以下功能:
- 身份验证:拦截器可以检查用户的身份是否合法,例如检查用户是否登录、是否具有访问权限等。
- 日志记录:拦截器可以记录请求的日志信息,例如记录请求的URL、请求参数、响应结果等。
- 异常处理:拦截器可以捕获并处理异常,例如记录异常信息、返回错误页面等。
- 数据转换:拦截器可以对请求和响应的数据进行转换,例如将请求参数转换为对象、将对象转换为响应数据等。
- 数据校验:拦截器可以对请求数据进行校验,例如检查请求参数是否符合要求等。
- 数据过滤:拦截器可以对请求数据进行过滤,例如过滤掉一些敏感信息等。
拦截器的实现
环境准备
点击查看代码
pom.xml添加SSM整合所需jar包
1 |
|
创建对应的配置类
1 | public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { |
创建模型类Book
1 | public class Book { |
编写Controller
1 |
|
拦截器的实现步骤
步骤1:创建拦截器类
让类实现HandlerInterceptor接口,重写接口中的三个方法。
1 |
|
注意:拦截器类要被SpringMVC容器扫描到。
步骤2:配置拦截器类
1 |
|
步骤3:SpringMVC添加SpringMvcSupport包扫描1
2
3
4
5
6
public class SpringMvcConfig{
}
步骤5:修改拦截器拦截规则
1 |
|
步骤6:简化SpringMvcSupport的编写
1 |
|
此后咱们就不用再写SpringMvcSupport类了。
拦截器参数
前置处理方法
原始方法之前运行preHandle
1 | public boolean preHandle(HttpServletRequest request, |
- request:请求对象
- response:响应对象
- handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装
使用request对象可以获取请求数据中的内容,如获取请求头的Content-Type
1 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
使用handler参数,可以获取方法的相关信息
1 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
后置处理方法
原始方法运行后运行,如果原始方法被拦截,则不执行
1 | public void postHandle(HttpServletRequest request, |
前三个参数和上面的是一致的。
modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
因为咱们现在都是返回json数据,所以该参数的使用率不高。
完成处理方法
拦截器最后执行的方法,无论原始方法是否执行
1 | public void afterCompletion(HttpServletRequest request, |
前三个参数与上面的是一致的。
ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
因为我们现在已经有全局异常处理器类,所以该参数的使用率也不高。
这三个方法中,最常用的是==preHandle==,在这个方法中可以通过返回值来决定是否要进行放行,我们可以把业务逻辑放在该方法中,如果满足业务则返回true放行,不满足则返回false拦截。
拦截器链配置
目前,我们在项目中只添加了一个拦截器,如果有多个,该如何配置?配置多个后,执行顺序是什么?
配置多个拦截器
步骤1:创建拦截器类
实现接口,并重写接口中的方法
1 |
|
步骤2:配置拦截器类
1 |
|
拦截器执行的顺序是和配置顺序有关。就和前面所提到的运维人员进入机房的案例,先进后出。
- 当配置多个拦截器时,形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作
preHandle:与配置顺序相同,必定运行
postHandle:与配置顺序相反,可能不运行
afterCompletion:与配置顺序相反,可能不运行。



