SpringMVC
一、SpringMVC介绍
概述
Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。
查看官方文档:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html
SpringMVC 的优点
Spring MVC的特点:
- 轻量级,简单易学
- 高效 , 基于请求响应的MVC框架
- 与Spring兼容性好,无缝结合
- 约定优于配置
- 功能强大:RESTful、数据验证、格式化、本地化、主题等
- 简洁灵活
Spring的web框架围绕DispatcherServlet
[ 调度Servlet ] 设计。
DispatcherServlet
的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁;
正因为SpringMVC 简单 , 便捷 , 易学 , 天生和Spring无缝集成(使用SpringIoC和Aop) , 使用约定优于配置 . 能够进行简单的junit测试 . 支持Restful风格 .异常处理 , 本地化 , 国际化 , 数据验证 , 类型转换 , 拦截器 等等……所以我们要学习 .
二、🌈SpringMVC注解
@RequestMapping
@RequestMapping
注解用于映射url到控制器类或一个特定的处理程序方法。
可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径
@RequestMapping
作用在类和方法上的区别
在方法上使用: 访问路径 http://localhost:8080/h1
1
2
3
4
5
6
7
public class TestController {
public String test(){
return "hello";
}
}注解在类和方法上, 访问路径: http://localhost:8080/admin/h1
1
2
3
4
5
6
7
8
public class TestController {
public String test(){
return "hello";
}
}
一般用于区分各个模块中的请求
路径和通配符
通配符使用
*
匹配任意多个字符(0~N); 不能匹配多个路径**
: 匹配任意多层路径?
: 匹配任意单个字符(1)
使用通配符后, 若能匹配到多个路径:
- 精确优先
- 精确程度: 完全匹配 >
?
>*
>**
1 |
|
请求限定
请求方式 :
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
1
2
3
4
public String test01(){
return "hello world";
}请求参数
params = {"age=18","username","gender!=1"}
表示表示请求必须包含username参数
age=18
: 表示请求参数中必须包含age=18的参数gender!=1
: 表示请求参数中不能包含gender=1的参数
1
2
3
4
5
6
7
8
9
10/**
* 请求参数:params = {"username","age"}
* 1)、username: 表示请求必须包含username参数
* 2)、age=18: 表示请求参数中必须包含age=18的参数
* 3)、gender!=1: 表示请求参数中不能包含gender=1的参数
*/
public String test02(){
return "test02";
}请求头
headers = {"haha"}
表示的含义:haha
: 表示请求中必须包含名为haha的请求头hehe!=1
: 表示请求头中 的 hehe 不能是1
1
2
3
4
public String test03(){
return "test03";
}请求内容类型
consumes = {"application/json"}
表示的含义consumes
请求内容类型:消费什么数据; Media Type:媒体类型application/json
: 表示浏览器必须携带 json 格式的数据。
1
2
3
4
public String test04(){
return "test04";
}响应类型限定
produces = {"text/plain;charset=utf-8"}
生产什么数据;1
2
3
4
public String test05(){
return "<h1>你好,张三</h1>";
}
@RequestMapping注解的变体:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@RequestMapping(name = "/add",method = RequestMethod.GET)
方式等同与 @GetMapping("/add")
@RestController
注解说明
@RestController
= @Controller
+ @ResponseBody
@RestController
是 Spring MVC 中的注解,是 @Controller 注解的一个变体。作用为标识类为 RESTful 风格的控制器组件,专门用于处理 HTTP 请求并返回数据(通常是 JSON 格式)。
@Controller
标识类为 Spring MVC 控制器组件,用于处理请求。
@ResponseBody
注解用于把返回值放到响应体中
三、⭐请求和响应处理
HTTP请求与响应
HTTP请求会带来各种数据
- 请求首行:(请求方式、请求路径、请求协议)
- 请求头:(k: v k: v)
- 请求体:(此次请求携带的其他数据)
URL 携带大量数据,特别是GET请求,会把参数放在URL上
请求头 有很多重要信息,SpringMVC 可以快速获取到
请求体 携带大量数据,特别是POST请求,会把参数放在请求体中
⭐JSON
什么是JSON?
- JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
- 采用完全独立于编程语言的文本格式来存储和表示数据。
- 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
- 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
语法
在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:
- 对象表示为键值对,数据由逗号分隔
- 花括号保存对象
- 方括号保存数组
JSON 的表示
JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 “” 包裹,使用冒号 : 分隔,然后紧接着值:
1 | {"name": "QinJiang"} |
备注:将字符串转换为原生对象称为反序列化(deserialization),
而将原生对象转换为可以通过网络传输的字符串称为序列化(serialization
⭐请求处理
URL 中请携带的参数
1 |
|
@RequestParm
使用
@RequestParm
明确指定获取哪个参数的值
无论请求参数是在url 中还是在请求体中, 都可以使用@RequestParm
或 同一个变量名获取
1 |
|
将请求参数封装到实体类中
准备实体类, 属性名需要和参数名一致(所有的类型都尽量使用包装类)
1
2
3
4
5
6
7
public class Person {
private String username;
private String password;
private String cellphone;
private Boolean agreement;
}将参数封装到实体类中
1
2
3
4
5
public String handle03(Person person) {
System.out.println(person);
return "OK";
}
@RequestHeader
@RequestHeader
用于获取请求头数据
1 |
|
@CookieValue
@CookieValue
用于获取Cookie数据
1 |
|
⭐使用实体类级联封装复杂属性
需要封装的参数
1
http://localhost:8080/handle06?username=xiaoyu&password=123456&cellphone=&address.province=陕西&address.city=西安&address.area=鄠邑&sex=男&hobby=篮球&hobby=足球&grade=一年级&agreement=on
根据参数编写实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Person {
private String username;
private String password;
private String cellphone;
private Boolean agreement;
private Address address;
private String sex;
private String grade;
private String[] hobby;
}
class Address {
private String province;
private String city;
private String area;
}使用对象来封装请求参数
1
2
3
4
5
public String handle06(Person person) {
System.out.println(person);
return "OK";
}
⭐
@RequestBody
,封装JSON对象
@RequestBody
用于将JSON参数封装为实体类对象
使用postman 发送 请求体中含有json字符串的请求
@RequestBody
可以获取请求体中的json字符串, 并将json字符串转为对象1
2
3
4
5
6
public String handle07({ Person person)
System.out.println(person);
//自己把字符串转为对象。
return "ok";
}
文件上传
MultipartFile
专门用于封装文件项
@RequestPart
/@RequestParam
用于封装文件
1 |
|
注意:
- Spring MVC 默认限制上传文件大小为1M, 可以在
application.yaml
配置文件中修改
HttpEntity
HttpEntity 用于封装请求头和请求体(把整个请求拿过来)
泛型:<String>
请求体类型: 可以把请求体中的数据自动转化泛型类型
1 |
|
获取servlet 原生API
可以获取Servlet 的原生API, 如HttpServletRequest
、 HttpServletResponse
、 HttpSession
1 |
|
响应处理
将数据以JSON格式写到响应体中
在类上标注@RestController
注解, 即可以JSON 格式响应数据
1 |
|
文件下载
HttpEntity
:拿到整个请求数据ResponseEntity
:拿到整个响应数据(响应头、响应体、状态码)
1 |
|
四、⭐RestFul 风格
基本介绍
概念
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
功能
资源:互联网所有的事物都可以被抽象为资源
资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
分别对应 添加、 删除、修改、查询。
传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get
- http://localhost/getEmployee?id=1 查询, GET
- http://localhost//getEmployeeList 查询所有, GET
- http://localhost/addEmployee?name=zhangsan&age=18 新增, POST
- http://localhost//updateEmployee?id=1&age=20 更新, POST
- http://localhost/item/deleteEmployee?id= 删除, GET或POST
使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!
如下:请求地址一样,但是功能可以不同!
- http://localhost//employee/1 查询,
GET
- http://localhost//employee 新增,
POST
- http://localhost//employee 更新,
PUT
- http://localhost//employee/1 删除,
DELETE
使用
RestFul接口案例
1 |
|
@PathVariable
路径变量
@PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。
1 |
|
使用路径变量的好处?
- 使路径变得更加简洁;
- 获得参数更加方便,Spring MVC会自动进行类型转换。
- 通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径是
/add/1/a
,则路径与方法不匹配,而不会是参数转换失败。
五、🌈拦截器
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。
SpringMVC 拦截器,允许在请求被目标方法处理的前后进行拦截,执行一些额外操作;比如:权限验证、日志记录、数据共享等…
基本使用
使用步骤
- 实现
HandlerInterceptor
接口的组件即可成为拦截器 - 创建 WebMvcConfigurer 组件,并配置拦截器的拦截路径
- 查看执行顺序效果:
preHandle => 目标方法 => postHandle => afterCompletion
案例
实现
HandlerInterceptor
接口的组件即可成为拦截器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
System.out.println("preHandle......");
return true; // 放行拦截的请求
}
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
System.out.println("postHandle......");
}
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
System.out.println("afterCompletion......");
}
}创建 WebMvcConfigurer 组件,并配置拦截器的拦截路径
1
2
3
4
5
6
7
8
9
10
11
public class WebMvcConfig implements WebMvcConfigurer {
MyInterceptor interceptor;
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(interceptor)
.addPathPatterns("/**"); // 拦截所有请求
}
}查看结果
拦截器的执行顺序
拦截器执行顺序:顺序preHandle => 目标方法 => 倒序postHandle => 渲染 => 倒序afterCompletion
- 只有执行成功的 preHandle 会倒序执行 afterCompletion
- postHandle 、afterCompletion 从哪里炸,倒序链路从哪里结束
- postHandle 失败不会影响 afterCompletion 执行
拦截器和过滤器的区别
拦截器 | 过滤器 | |
---|---|---|
接口 | HandlerInterceptor | Filter |
定义 | Spring 框架 | Servlet 规范 |
放行 | preHandle 返回 true 放行请求 | chain.doFilter() 放行请求 |
整合性 | 可以直接整合Spring容器的所有组件 | 不受Spring容器管理,无法直接使用容器中组件 需要把它放在容器中,才可以继续使用 |
拦截范围 | 拦截 SpringMVC 能处理的请求 | 拦截Web应用所有请求 |
总结 | SpringMVC的应用中,推荐使用拦截器 |
过滤器与拦截器的区别:拦截器是AOP思想的具体应用。
过滤器
- servlet规范中的一部分,任何java web工程都可以使用
- 在
url-pattern
中配置了/*之后,可以对所有要访问的资源进行拦截
拦截器
- 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
- 拦截器只会拦截访问的控制器方法, 如果访问的是静态资源是不会进行拦截的
六、异常处理
声名式异常处理
声名式异常处理和编程式异常处理
编程式异常处理:
try - catch、throw
声明式异常处理:
- SpringMVC 提供了
@ExceptionHandler
、@ControllerAdvice
等便捷的声明式注解来进行快速的异常处理 @ExceptionHandler
:可以处理指定类型异常@ControllerAdvice
:可以集中处理所有Controller的异常@ExceptionHandler
+@ControllerAdvice
: 可以完成全局统一异常处理
- 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
public class CustomException extends Exception{
// 状态码
private final int code;
// 信息
private final String message;
// 描述
private final String description;
public CustomException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.message = errorCode.getMessage();
this.code = errorCode.getCode();
this.description = errorCode.getDescription();
}
public CustomException(int code, String message, String description) {
super(message);
this.code = code;
this.message = message;
this.description = description;
}
}定义业务错误状态码(枚举类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 设置get方法
public enum ErrorCode {
SUCCESS(2000, "OK", ""),
PARAMS_ERROR(40000, "请求参数错误", ""),
NULL_ERROR(40001, "请求参数为空", ""),
NOT_LOGIN(40100, "未登录", ""),
NO_AUTH(40101, "无权限", ""),
SYSTEM_ERROR(50000, "系统内部异常", "");
final int code;
final String message;
final String description;
ErrorCode(int code, String message, String description) {
this.code = code;
this.message = message;
this.description = description;
}
}编写全局异常处理器
GlobalExceptionHandler
, 处理所有异常,返回给前端约定的json数据与错误码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 全局统一异常处理(controller 层)
public class GlobalExceptionHandler {
public BaseResponse<Object> customException(CustomException e) {
log.error("CustomException -> " + e.getMessage() );
return ResultUtils.error(e.getCode(), e.getMessage(), e.getDescription());
}
/**
* 全局的运行时异常处理器
*/
public BaseResponse<Object> customExceptionHandler(RuntimeException e) {
log.error("RuntimeException", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR);
}
}编写业务代码的时候,只需要编写正确逻辑,如果出现预期的问题,需要以抛异常的方式中断逻辑并通知上层。
七、JSR 303 数据校验
JSR 303 是 Java 为 Bean 数据合法性校验 提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上 标注 类似于 @NotNull
、@Max
等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
使用
数据校验使用流程
引入校验依赖
1
2
3
4
5<!--JSR303-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>定义封装数据的Bean
给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
27
28
public class EmployeeUpdateVo implements Serializable {
private Long id;
// 员工名字
private String name;
// 年龄
private Integer age;
// 邮箱
private String email;
// 性别
private String gender;
// 住址
private String address;
// 薪资
private BigDecimal salary;
}使用@Valid、@Validated开启校验
1
2
3
4
5
6
7
8
public BaseResponse<Integer> updateEmployee(
EmployeeUpdateVo employeeUpdateVo) throws CustomException {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeUpdateVo, employee);
return ResultUtils.success(employeeService.updateEmployeeByID(employee));
}使用自定义校验注解 + 校验器(
implements ConstraintValidator
) 完成gender字段自定义校验规则// todo
结合全局异常处理,统一处理数据校验错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public BaseResponse
methodArgumentNotValidException(MethodArgumentNotValidException ex) {
//1、result 中封装了所有错误信息
BindingResult result = ex.getBindingResult();
List<FieldError> errors = result.getFieldErrors();
Map<String, String> map = new HashMap<>();
for (FieldError error : errors) {
String field = error.getField();
String message = error.getDefaultMessage();
map.put(field, message);
}
return ResultUtils.error(ErrorCode.PARAMS_ERROR, map);
}
常用的校验注解
校验注解 | 作用 |
---|---|
@Min(value) |
验证数字是否大于等于指定的最小值 |
@Max(value) |
验证数字是否小于等于指定的最大值 |
@Null |
验证对象是否为null |
@NotNull |
验证对象是否不为null, 与@Null相反**(a!=null)** |
@NotEmpty |
验证字符串是否非空**(a!=null && a!=“”)** |
@NotBlank |
验证字符串是否非空白字符**(a!=null && a.trim().length > 0)** |
@Size(max=, min=) |
验证字符串、集合、Map、数组的大小是否在指定范围内 |
@Pattern(regex=, flag=) |
验证字符串是否符合指定的正则表达式 |
自定义校验器
- 自定义注解
- 自定义校验器
八、✅Knife4j 接口文档
Knife4j是对Swagger文档的增强解决方案,底层是对Springfox的(boot整合swagger)的封装,是对接口文档UI进行优化,注解与Swagger相同
核心功能
文档说明:根据Swagger的规范说明,详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息,对该接口的使用情况一目了然。
在线调试:提供在线接口联调的强大功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、响应时间、响应状态码等信息,帮助开发者在线调试。
Knife4j 配置使用
注意: 所使用的 SpringBoot 版本为3.3.3
在pom.xml 引入所需依赖
1
2
3
4
5
6<!-- knife4j 接口文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>在项目的的config配置中创建SwaggerConfig
此处要千万注意: 线上环境不要把接口暴露出去!!!
1
// todo 此项不配置也可使用
在application.yaml中添加配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# springdoc-openapi项目配置
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
api-docs:
path: /v3/api-docs
group-configs:
- group: 'default'
paths-to-match: '/**'
packages-to-scan: com.xiaoyu.controller
# knife4j的增强配置,不需要增强可以不配
knife4j:
enable: true
setting:
language: zh_cn启动项目, swagger访问地址 http://localhost:8080/api/v1/doc.html
接口文档信息标注
常用注解 (SpringBoot3 ->
OpenAPI3
的规范注解)
注解 | 标注位置 | 作用 |
---|---|---|
@Tag |
Controller 类 | 描述 controller 作用 |
@Parameter |
参数 | 标识参数作用 |
@Parameters |
参数 | 参数多重说明 |
@Schema |
model 层的 JavaBean | 描述模型作用及每个属性 |
@Operation |
方法 | 描述方法作用 |
@ApiResponse |
方法 | 描述响应状态码等 |
注解使用
1 |
|
请求参数是对象时, 可以标注在对象上
1 |
|
九、Spring MVC 原理
中心控制器
Spring的web框架围绕DispatcherServlet
设计。DispatcherServlet的作用是将请求分发到不同的处理器。
SpringMVC框架像许多其他MVC框架一样, 以请求为驱动 , 围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet是一个实际的Servlet (它继承自HttpServlet 基类)。
SpringMVC的原理如下图所示:
当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。
⭐SpringMVC执行原理
执行流程
图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。
简要分析执行流程
DispatcherServlet表示前端控制器,是整个SpringMVC的控制中心。用户发出请求,
DispatcherServlet
接收请求并拦截请求。我们假设请求的url为 : http://localhost:8080/SpringMVC/hello 将url拆分成三部分:
SpringMVC部署在服务器上的web站点
hello表示控制器
通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。
HandlerMapping
为处理器映射。DispatcherServlet调用HandlerMapping, HandlerMapping根据请求url查找Handler。HandlerExecution
表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为: helloHandlerExecution
将解析后的信息传递给DispatcherServlet,如解析控制器映射等。HandlerAdapter
表示处理器适配器,其按照特定的规则去执行Handler。Handler让具体的Controller执行。
Controller将具体的执行信息返回给
HandlerAdapter
,如ModelAndViewHandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。
DispatcherServlet
调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。视图解析器将解析的逻辑视图名传给DispatcherServlet。
DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。
最终视图呈现给用户。