2025-03-18 12:46:59 +08:00
|
|
|
|
# JavaWeb——后端
|
|
|
|
|
|
2025-06-06 19:28:06 +08:00
|
|
|
|
## 好用的操作
|
|
|
|
|
|
|
|
|
|
右键文件/文件夹选择Copy Path/Reference,可以获得完整的包路径
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
## Java版本解决方案
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
**单个Java文件运行:**
|
|
|
|
|
|
|
|
|
|
Edit Configurations
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
- **针对单个运行配置**:每个 Java 运行配置(如主类、测试类等)可以独立设置其运行环境(如 JRE 版本、程序参数、环境变量等)。
|
|
|
|
|
- **不影响全局项目**:修改某个运行配置的环境不会影响其他运行配置或项目的全局设置。
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
**如何调整全局项目的环境**
|
|
|
|
|
|
|
|
|
|
- 打开 `File -> Project Structure -> Project`。
|
|
|
|
|
- 在 `Project SDK` 中选择全局的 JDK 版本(如 JDK 17)。
|
|
|
|
|
- 在 `Project language level` 中设置全局的语言级别(如 17)。
|
|
|
|
|
|
|
|
|
|
**Java Compiler**
|
|
|
|
|
|
|
|
|
|
- `File -> Settings -> Build, Execution, Deployment -> Compiler -> Java Compiler`
|
|
|
|
|
|
|
|
|
|
**Maven Runner**
|
|
|
|
|
|
|
|
|
|
- `File -> Settings -> Build, Execution, Deployment -> Build Tools -> Maven -> Runner`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
**三者之间的关系**
|
|
|
|
|
|
|
|
|
|
- 全局项目环境 是基准,决定项目的默认 JDK 和语言级别。
|
|
|
|
|
- Java Compiler 控制编译行为,可以覆盖全局的 `Project language level`。
|
|
|
|
|
- Maven Runner 控制 Maven 命令的运行环境,可以覆盖全局的 `Project SDK`。
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Maven 项目**:
|
|
|
|
|
|
|
|
|
|
- 确保 `pom.xml` 中的 `<maven.compiler.source>` 和 `<maven.compiler.target>` 与 `Project SDK` 和 `Java Compiler` 的配置一致。
|
|
|
|
|
- 确保 `Maven Runner` 中的 `JRE` 与 `Project SDK` 一致。
|
|
|
|
|
- 如果还是不行,pom文件右键点击maven->reload project
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## HTTP协议
|
|
|
|
|
|
|
|
|
|
### 响应状态码
|
|
|
|
|
|
|
|
|
|
| 状态码分类 | 说明 |
|
|
|
|
|
| ---------- | ------------------------------------------------------------ |
|
|
|
|
|
| 1xx | **响应中** --- 临时状态码。表示请求已经接受,告诉客户端应该继续请求或者如果已经完成则忽略 |
|
|
|
|
|
| 2xx | **成功** --- 表示请求已经被成功接收,处理已完成 |
|
|
|
|
|
| 3xx | **重定向** --- 重定向到其它地方,让客户端再发起一个请求以完成整个处理 |
|
|
|
|
|
| 4xx | **客户端错误** --- 处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等 |
|
|
|
|
|
| 5xx | **服务器端错误** --- 处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 17:35:22 +08:00
|
|
|
|
| 状态码 | 英文描述 | 解释 |
|
|
|
|
|
| ------- | --------------------------- | ------------------------------------------------------------ |
|
|
|
|
|
| ==200== | **`OK`** | 客户端请求成功,即**处理成功**,这是我们最想看到的状态码 |
|
|
|
|
|
| 302 | **`Found`** | 指示所请求的资源已移动到由`Location`响应头给定的 URL,浏览器会自动重新访问到这个页面 |
|
|
|
|
|
| 304 | **`Not Modified`** | 告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向 |
|
|
|
|
|
| 400 | **`Bad Request`** | 客户端请求有**语法错误**,不能被服务器所理解 |
|
|
|
|
|
| 403 | **`Forbidden`** | 服务器收到请求,但是**拒绝提供服务**,比如:没有权限访问相关资源 |
|
|
|
|
|
| ==404== | **`Not Found`** | **请求资源不存在**,一般是URL输入有误,或者网站资源被删除了 |
|
|
|
|
|
| 405 | **`Method Not Allowed`** | 请求方式有误,比如应该用GET请求方式的资源,用了POST |
|
|
|
|
|
| 429 | **`Too Many Requests`** | 指示用户在给定时间内发送了**太多请求**(“限速”),配合 Retry-After(多长时间后可以请求)响应头一起使用 |
|
|
|
|
|
| ==500== | **`Internal Server Error`** | **服务器发生不可预期的错误**。服务器出异常了,赶紧看日志去吧 |
|
|
|
|
|
| 503 | **`Service Unavailable`** | **服务器尚未准备好处理请求**,服务器刚刚启动,还未初始化好 |
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 开发规范
|
|
|
|
|
|
|
|
|
|
### REST风格
|
|
|
|
|
|
|
|
|
|
在前后端进行交互的时候,我们需要基于当前主流的REST风格的API接口进行交互。
|
|
|
|
|
|
|
|
|
|
什么是REST风格呢?
|
|
|
|
|
|
|
|
|
|
- REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格。
|
|
|
|
|
|
|
|
|
|
**传统URL风格如下:**
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-26 09:31:51 +08:00
|
|
|
|
http://localhost:8080/user/getById?id=1 GET:查询id为1的用户
|
|
|
|
|
http://localhost:8080/user/saveUser POST:新增用户
|
|
|
|
|
http://localhost:8080/user/updateUser PUT:修改用户
|
|
|
|
|
http://localhost:8080/user/deleteUser?id=1 DELETE:删除id为1的用户
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
我们看到,原始的传统URL,定义比较复杂,而且将资源的访问行为对外暴露出来了。
|
|
|
|
|
|
|
|
|
|
**基于REST风格URL如下:**
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-26 09:31:51 +08:00
|
|
|
|
http://localhost:8080/users/1 GET:查询id为1的用户
|
|
|
|
|
http://localhost:8080/users POST:新增用户
|
|
|
|
|
http://localhost:8080/users PUT:修改用户
|
|
|
|
|
http://localhost:8080/users/1 DELETE:删除id为1的用户
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
其中总结起来,就一句话:通过URL定位要操作的资源,通过HTTP动词(请求方式)来描述具体的操作。
|
|
|
|
|
|
|
|
|
|
**REST风格后端代码:**
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-26 09:31:51 +08:00
|
|
|
|
@RestController
|
|
|
|
|
@RequestMapping("/depts") //定义当前控制器的请求前缀
|
|
|
|
|
public class DeptController {
|
|
|
|
|
|
|
|
|
|
// GET: 查询资源
|
|
|
|
|
@GetMapping("/{id}")
|
|
|
|
|
public Dept getDept(@PathVariable Long id) { ... }
|
|
|
|
|
|
|
|
|
|
// POST: 新增资源
|
|
|
|
|
@PostMapping
|
|
|
|
|
public void createDept(@RequestBody Dept dept) { ... }
|
|
|
|
|
|
|
|
|
|
// PUT: 更新资源
|
|
|
|
|
@PutMapping
|
|
|
|
|
public void updateDept(@RequestBody Dept dept) { ... }
|
|
|
|
|
|
|
|
|
|
// DELETE: 删除资源
|
|
|
|
|
@DeleteMapping("/{id}")
|
|
|
|
|
public void deleteDept(@PathVariable Long id) { ... }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
**GET**:查询,用 URL 传参,不能带 body。
|
|
|
|
|
|
|
|
|
|
**POST**:创建/提交,可以用 body 传数据(JSON、表单)。
|
|
|
|
|
|
|
|
|
|
**PUT**:更新,可以用 body 。
|
|
|
|
|
|
|
|
|
|
**DELETE**:删除,一般无 body,只要 `-X DELETE`。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
### 开发流程
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
1. 查看页面原型明确需求
|
|
|
|
|
- 根据页面原型和需求,进行表结构设计、编写接口文档(已提供)
|
|
|
|
|
|
|
|
|
|
2. 阅读接口文档
|
|
|
|
|
3. 思路分析
|
|
|
|
|
4. 功能接口开发
|
|
|
|
|
- 就是开发后台的业务功能,一个业务功能,我们称为一个接口(Controller 中一个完整的处理请求的方法)
|
|
|
|
|
5. 功能接口测试
|
|
|
|
|
- 功能开发完毕后,先通过Postman进行功能接口测试,测试通过后,再和前端进行联调测试
|
|
|
|
|
6. 前后端联调测试
|
|
|
|
|
- 和前端开发人员开发好的前端工程一起测试
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
## SpringBoot
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
**Servlet 容器** 是用于管理和运行 Web 应用的环境,它负责加载、实例化和管理 Servlet 组件,处理 HTTP 请求并将请求分发给对应的 Servlet。常见的 Servlet 容器包括 Tomcat、Jetty、Undertow 等。
|
|
|
|
|
|
2025-03-18 17:35:22 +08:00
|
|
|
|
**SpringBoot的WEB默认内嵌了tomcat服务器,非常方便!!!**
|
|
|
|
|
|
|
|
|
|
浏览器与 Tomcat 之间通过 HTTP 协议进行通信,而 Tomcat 则充当了中间的桥梁,将请求路由到你的 Java 代码,并最终将处理结果返回给浏览器。
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-15 12:46:07 +08:00
|
|
|
|
查看springboot版本:查看pom文件
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
<parent>
|
|
|
|
|
<artifactId>spring-boot-starter-parent</artifactId>
|
|
|
|
|
<groupId>org.springframework.boot</groupId>
|
|
|
|
|
<version>2.7.3</version>
|
|
|
|
|
</parent>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
版本为2.7.3
|
|
|
|
|
|
2025-06-06 19:28:06 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
### 快速启动
|
|
|
|
|
|
2025-04-02 18:28:46 +08:00
|
|
|
|
1. 新建**spring initializr** project
|
2025-03-18 12:46:59 +08:00
|
|
|
|
2. 删除以下文件
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
新建HelloController类
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
package edu.whut.controller;
|
|
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
|
|
|
|
@RestController
|
|
|
|
|
public class HelloController {
|
|
|
|
|
@RequestMapping("/hello")
|
|
|
|
|
public String hello(){
|
|
|
|
|
System.out.println("hello");
|
|
|
|
|
return "hello";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
然后启动服务器,main程序
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
package edu.whut;
|
|
|
|
|
|
|
|
|
|
import org.springframework.boot.SpringApplication;
|
|
|
|
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
|
|
|
|
|
|
|
|
@SpringBootApplication
|
|
|
|
|
public class SprintbootQuickstartApplication {
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
SpringApplication.run(SprintbootQuickstartApplication.class, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
然后浏览器访问 localhost:8080/hello。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### SpringBoot请求
|
|
|
|
|
|
|
|
|
|
#### 简单参数
|
|
|
|
|
|
|
|
|
|
- 在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。 如果是简单参数,参数名与形参变量名相同,定义同名的形参即可接收参数。
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@RestController
|
|
|
|
|
public class RequestController {
|
|
|
|
|
// http://localhost:8080/simpleParam?name=Tom&age=10
|
|
|
|
|
// 第1个请求参数: name=Tom 参数名:name,参数值:Tom
|
|
|
|
|
// 第2个请求参数: age=10 参数名:age , 参数值:10
|
|
|
|
|
|
|
|
|
|
//springboot方式
|
|
|
|
|
@RequestMapping("/simpleParam")
|
|
|
|
|
public String simpleParam(String name , Integer age ){//形参名和请求参数名保持一致
|
|
|
|
|
System.out.println(name+" : "+age);
|
|
|
|
|
return "OK";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 如果方法形参名称与请求参数名称不一致,controller方法中的形参还能接收到请求参数值吗?
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
解决方案:可以使用Spring提供的`@RequestParam`注解完成映射
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
在方法形参前面加上 `@RequestParam` 然后通过value属性执行请求参数名,从而完成映射。代码如下:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@RestController
|
|
|
|
|
public class RequestController {
|
|
|
|
|
// http://localhost:8080/simpleParam?name=Tom&age=20
|
|
|
|
|
// 请求参数名:name
|
|
|
|
|
|
|
|
|
|
//springboot方式
|
|
|
|
|
@RequestMapping("/simpleParam")
|
|
|
|
|
public String simpleParam(@RequestParam("name") String username , Integer age ){
|
|
|
|
|
System.out.println(username+" : "+age);
|
|
|
|
|
return "OK";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
#### 实体参数
|
|
|
|
|
|
|
|
|
|
复杂实体对象指的是,在实体类中有一个或多个属性,也是实体对象类型的。如下:
|
|
|
|
|
|
|
|
|
|
- User类中有一个Address类型的属性(Address是一个实体类)
|
|
|
|
|
|
|
|
|
|
复杂实体对象的封装,需要遵守如下规则:
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
- **请求参数名与形参对象属性名相同**,按照对象层次结构关系即可接收嵌套实体类属性参数。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
注意:这里User前面不能加`@RequestBody`是因为请求方式是 **Query** 或 **路径** 参数;如果是JSON请求体(**Body**)就必须加。
|
|
|
|
|
|
|
|
|
|
<img src="https://pic.bitday.top/i/2025/04/15/nxkm0y-0.png" alt="image-20250415144710544" style="zoom:80%;" />
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@RequestMapping("/complexpojo")
|
|
|
|
|
public String complexpojo(User user){
|
|
|
|
|
System.out.println(user);
|
|
|
|
|
return "OK";
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 17:35:22 +08:00
|
|
|
|
@Data
|
|
|
|
|
@NoArgsConstructor
|
|
|
|
|
@AllArgsConstructor
|
2025-03-18 12:46:59 +08:00
|
|
|
|
public class User {
|
|
|
|
|
private String name;
|
|
|
|
|
private Integer age;
|
|
|
|
|
private Address address;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 17:35:22 +08:00
|
|
|
|
@Data
|
|
|
|
|
@NoArgsConstructor
|
|
|
|
|
@AllArgsConstructor
|
2025-03-18 12:46:59 +08:00
|
|
|
|
public class Address {
|
|
|
|
|
private String province;
|
|
|
|
|
private String city;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
#### 数组参数
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
数组参数:请求参数名与形参数组**名称相同**且请求参数为**多个**,定义数组类型形参即可接收参数
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@RestController
|
|
|
|
|
public class RequestController {
|
|
|
|
|
//数组集合参数
|
|
|
|
|
@RequestMapping("/arrayParam")
|
|
|
|
|
public String arrayParam(String[] hobby){
|
|
|
|
|
System.out.println(Arrays.toString(hobby));
|
|
|
|
|
return "OK";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 路径参数
|
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
请求的URL中传递的参数 称为路径参数。例如:
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
~~~text
|
|
|
|
|
http://localhost:8080/user/1
|
|
|
|
|
http://localhost:880/user/1/0
|
|
|
|
|
~~~
|
|
|
|
|
|
2025-03-29 11:33:34 +08:00
|
|
|
|
注意,路径参数使用大括号 `{}` 定义
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
```java
|
|
|
|
|
@RestController
|
|
|
|
|
public class RequestController {
|
|
|
|
|
//路径参数
|
|
|
|
|
@RequestMapping("/path/{id}/{name}")
|
|
|
|
|
public String pathParam2(@PathVariable Integer id, @PathVariable String name){
|
|
|
|
|
System.out.println(id+ " : " +name);
|
|
|
|
|
return "OK";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
在路由定义里用 `{id}` 只是一个占位符,实际请求时 **不要** 带大括号
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
#### JSON格式参数
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
{
|
2025-03-26 09:31:51 +08:00
|
|
|
|
"backtime": [
|
2025-03-18 12:46:59 +08:00
|
|
|
|
"与中标人签订合同后 5日内",
|
|
|
|
|
"投标截止时间前撤回投标文件并书面通知招标人的,2日内",
|
2025-03-26 09:31:51 +08:00
|
|
|
|
"开标现场投标文件被拒收,开标结束后,2日内"
|
2025-03-18 12:46:59 +08:00
|
|
|
|
],
|
|
|
|
|
"employees": [
|
|
|
|
|
{ "firstName": "John", "lastName": "Doe" },
|
|
|
|
|
{ "firstName": "Anna", "lastName": "Smith" },
|
|
|
|
|
{ "firstName": "Peter", "lastName": "Jones" }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
**JSON 格式的核心特征**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
- 接口文档中的请求参数中是 **'Body'** 发送数据
|
|
|
|
|
<img src="https://pic.bitday.top/i/2025/04/15/nwm01g-0.png" alt="image-20250415144541146" style="zoom:80%;" />
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
- 数据为键值对:数据存储在键值对中,键和值用冒号分隔。在你的示例中,每个对象有两个键值对,如 `"firstName": "John"`。
|
|
|
|
|
- 使用大括号表示对象:JSON 使用大括号 `{}` 包围对象,对象可以包含多个键值对。
|
|
|
|
|
- 使用方括号表示数组:JSON 使用方括号 `[]` 表示数组,数组中可以包含多个值,包括数字、字符串、对象等。在该示例中:`"employees"` 是一个对象数组,数组中的每个元素都是一个对象。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
Postman如何发送JSON格式数据:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
服务端Controller方法如何接收JSON格式数据:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
- 传递json格式的参数,在Controller中会使用实体类进行封装。
|
|
|
|
|
- 封装规则:JSON数据**键名与形参对象属性名**相同,定义POJO类型形参即可接收参数。需要使用 `@RequestBody`标识。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
```java
|
|
|
|
|
@Data
|
|
|
|
|
@NoArgsConstructor
|
|
|
|
|
@AllArgsConstructor
|
|
|
|
|
public class DataDTO {
|
|
|
|
|
private List<String> backtime;
|
|
|
|
|
private List<Employee> employees;
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
```java
|
|
|
|
|
@Data
|
|
|
|
|
@NoArgsConstructor
|
|
|
|
|
@AllArgsConstructor
|
|
|
|
|
public class Employee {
|
|
|
|
|
private String firstName;
|
|
|
|
|
private String lastName;
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@RestController
|
2025-03-26 09:31:51 +08:00
|
|
|
|
public class DataController {
|
|
|
|
|
|
|
|
|
|
@PostMapping("/data")
|
|
|
|
|
public String receiveData(@RequestBody DataDTO data) {
|
|
|
|
|
System.out.println("Backtime: " + data.getBacktime());
|
|
|
|
|
System.out.println("Employees: " + data.getEmployees());
|
2025-03-18 12:46:59 +08:00
|
|
|
|
return "OK";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
**JSON格式工具包**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
用于高效地进行 JSON 与 Java 对象之间的序列化和反序列化操作。
|
|
|
|
|
|
|
|
|
|
引入依赖:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>com.alibaba</groupId>
|
|
|
|
|
<artifactId>fastjson</artifactId>
|
|
|
|
|
<version>1.2.76</version>
|
|
|
|
|
</dependency>
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
使用:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
```java
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
public class FastJsonDemo {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
// 创建一个对象
|
|
|
|
|
User user = new User("Alice", 30);
|
|
|
|
|
|
|
|
|
|
// 对象转 JSON 字符串
|
|
|
|
|
String jsonString = JSON.toJSONString(user);
|
|
|
|
|
System.out.println("JSON String: " + jsonString);
|
|
|
|
|
|
|
|
|
|
// JSON 字符串转对象
|
|
|
|
|
User parsedUser = JSON.parseObject(jsonString, User.class);
|
|
|
|
|
System.out.println("Parsed User: " + parsedUser);
|
2025-03-18 12:46:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-26 09:31:51 +08:00
|
|
|
|
// JSON String: {"age":30,"name":"Alice"}
|
|
|
|
|
// Parsed User: User(name=Alice, age=30)
|
2025-03-18 12:46:59 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### SpringBoot响应
|
|
|
|
|
|
|
|
|
|
**@ResponseBody注解:**
|
|
|
|
|
|
|
|
|
|
- 位置:书写在Controller方法上或类上
|
|
|
|
|
- 作用:将方法返回值直接响应给浏览器
|
2025-03-26 09:31:51 +08:00
|
|
|
|
- 如果返回值类型是实体对象/集合,将会**转换为JSON格式**后在响应给浏览器
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
`@RestController` = `@Controller` + `@ResponseBody`
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**统一响应结果:**
|
|
|
|
|
|
|
|
|
|
下图返回值分别是字符串、对象、集合。
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**定义统一返回结果类**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
- 响应状态码:当前请求是成功,还是失败
|
|
|
|
|
|
|
|
|
|
- 状态码信息:给页面的提示信息
|
|
|
|
|
|
|
|
|
|
- 返回的数据:给前端响应的数据(字符串、对象、集合)
|
|
|
|
|
|
|
|
|
|
定义在一个实体类Result来包含以上信息。代码如下:
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@Data
|
|
|
|
|
@NoArgsConstructor
|
|
|
|
|
@AllArgsConstructor
|
|
|
|
|
public class Result {
|
|
|
|
|
private Integer code;//响应码,1 代表成功; 0 代表失败
|
|
|
|
|
private String msg; //响应信息 描述字符串
|
|
|
|
|
private Object data; //返回的数据
|
|
|
|
|
|
|
|
|
|
//增删改 成功响应
|
|
|
|
|
public static Result success(){
|
|
|
|
|
return new Result(1,"success",null);
|
|
|
|
|
}
|
|
|
|
|
//查询 成功响应
|
|
|
|
|
public static Result success(Object data){
|
|
|
|
|
return new Result(1,"success",data);
|
|
|
|
|
}
|
|
|
|
|
//失败响应
|
|
|
|
|
public static Result error(String msg){
|
|
|
|
|
return new Result(0,msg,null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Spring分层架构
|
|
|
|
|
|
|
|
|
|
#### 三层架构
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
Controller层接收请求,调用Service层;Service层先调用Dao层获取数据,然后实现自己的业务逻辑处理部分,最后返回给Controller层;Controller层再响应数据。可理解为递归的过程。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
传统模式:对象的创建、管理和依赖关系都由程序员手动编写代码完成,程序内部控制对象的生命周期。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
例如:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class A {
|
|
|
|
|
private B b;
|
|
|
|
|
public A() {
|
|
|
|
|
b = new B(); // A 自己创建并管理 B 的实例
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-18 12:46:59 +08:00
|
|
|
|
```
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
假设有类 A 依赖类 B,在传统方式中,类 A 可能在构造方法或方法内部直接调用 `new B()` 来创建 B 的实例。
|
|
|
|
|
|
|
|
|
|
如果 B 的创建方式发生变化,A 也需要修改代码。这就导致了耦合度较高。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
**软件设计原则:高内聚低耦合。**
|
|
|
|
|
|
|
|
|
|
> 高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 "高内聚"。
|
|
|
|
|
>
|
|
|
|
|
> 低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
#### IOC&DI 分层解耦
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
**外部容器**(例如 Spring 容器)是一个负责管理对象创建、配置和生命周期的软件系统。
|
|
|
|
|
|
|
|
|
|
- 它扫描项目中的类,根据预先配置或注解,将这些类实例化为 Bean。
|
|
|
|
|
- 它维护各个 Bean 之间的依赖关系,并在创建 Bean 时把它们所需的依赖“注入”进去。
|
|
|
|
|
|
|
|
|
|
**依赖注入(DI)**:类 A 不再自己创建 B,而是声明自己需要一个 B,容器在创建 A 时会自动将 B 的实例提供给 A。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class A {
|
|
|
|
|
private B b;
|
|
|
|
|
// 通过构造器注入依赖
|
|
|
|
|
public A(B b) {
|
|
|
|
|
this.b = b;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Bean 对象**:在 Spring 中,被容器管理的对象称为 Bean。通过注解(如 `@Component`, `@Service`, `@Repository`, `@Controller`),可以将一个普通的 Java 类声明为 Bean,容器会负责它的创建、初始化以及生命周期管理。
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|

|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
*任务:完成Controller层、Service层、Dao层的代码解耦*
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
思路:
|
|
|
|
|
1. 删除Controller层、Service层中new对象的代码
|
|
|
|
|
2. Service层及Dao层的**实现类**,交给IOC容器管理
|
|
|
|
|
3. 为Controller及Service注入运行时依赖的对象
|
|
|
|
|
- Controller程序中注入依赖的Service层对象
|
|
|
|
|
|
|
|
|
|
- Service程序中注入依赖的Dao层对象
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
第1步:删除Controller层、Service层中new对象的代码
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
第2步:Service层及Dao层的**实现类**,交给IOC容器管理
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
第3步:为Controller及Service注入运行时依赖的对象
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
使用Spring提供的注解:`@Autowired` ,就可以实现程序运行时IOC容器自动注入需要的依赖对象
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
**如果我有多个实现类,eg:EmpServiceA、EmpServiceB,我该如何切换呢?**两种方法
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
1. 只需在需要使用的实现类上加@Component,注释掉不需要用到的类上的@Component。可以把@Component想象成装入盒子,@Autowired想象成拿出来,因此只需改变放入的物品,而不需改变拿出来的这个动作。
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
// 只启用 EmpServiceA,其他实现类的 @Component 注解被注释或移除
|
|
|
|
|
@Component
|
|
|
|
|
public class EmpServiceA implements EmpService {
|
|
|
|
|
// 实现细节...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EmpServiceB 没有被 Spring 管理
|
|
|
|
|
// @Component
|
|
|
|
|
// public class EmpServiceB implements EmpService { ... }
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
2. 在@Component上面加上@Primary,表明该类优先生效
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```
|
|
|
|
|
// 默认使用 EmpServiceB,其他实现类也在容器中,但未标记为 Primary
|
|
|
|
|
@Component
|
|
|
|
|
public class EmpServiceA implements EmpService {
|
|
|
|
|
// 实现细节...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Component
|
|
|
|
|
@Primary // 默认优先注入
|
|
|
|
|
public class EmpServiceB implements EmpService {
|
|
|
|
|
// 实现细节...
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
Component衍生注解
|
|
|
|
|
|
|
|
|
|
| 注解 | 说明 | 位置 |
|
|
|
|
|
| :---------- | -------------------- | -------------------------------------------------- |
|
|
|
|
|
| @Controller | @Component的衍生注解 | 标注在控制器类上Controller |
|
|
|
|
|
| @Service | @Component的衍生注解 | 标注在业务类上Service |
|
|
|
|
|
| @Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,用的少)DAO |
|
|
|
|
|
| @Component | 声明bean的基础注解 | 不属于以上三类时,用此注解 |
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
注:@Mapper 注解本身并不是 Spring 框架提供的,是用于 *MyBatis* 数据层的接口标识,但效果类似。
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
## SpringBoot原理
|
|
|
|
|
|
|
|
|
|
### 容器启动
|
|
|
|
|
|
|
|
|
|
在 Spring 框架中,“容器启动”指的是 ApplicationContext 初始化过程,主要包括*配置解析*、*加载 Bean 定义*、*实例化*和*初始化 Bean* 以及完成*依赖注入*。具体来说,容器启动的时机包括以下几个关键点:
|
|
|
|
|
|
|
|
|
|
当你启动一个 Spring 应用时,无论是通过直接运行一个包含 `main` 方法的类,还是部署到一个 Servlet 容器中,Spring 的应用上下文都会被创建和初始化。这个过程包括:
|
|
|
|
|
|
|
|
|
|
- 读取配置:加载配置文件或注解中指定的信息,确定哪些组件由 Spring 管理。
|
|
|
|
|
- 注册 Bean 定义:将所有扫描到的 Bean 定义注册到容器中。
|
|
|
|
|
- 实例化 Bean:根据 Bean 定义创建实例。默认情况下,所有单例 Bean在启动时被创建(除非配置为懒加载)。
|
|
|
|
|
- 依赖注入:解析 Bean 之间的依赖关系,并自动注入相应的依赖。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-05-24 21:15:55 +08:00
|
|
|
|
### 依赖注入
|
|
|
|
|
|
2025-06-06 19:28:06 +08:00
|
|
|
|
**1.Autowird注入**
|
2025-05-24 21:15:55 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Autowired
|
|
|
|
|
private PaymentClient paymentClient; // 字段直接加 @Autowired
|
|
|
|
|
```
|
|
|
|
|
|
2025-06-06 19:28:06 +08:00
|
|
|
|
**2.构造器注入(推荐)**
|
2025-05-24 21:15:55 +08:00
|
|
|
|
|
|
|
|
|
1)手写构造器
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class OrderService {
|
|
|
|
|
|
|
|
|
|
private final PaymentClient paymentClient;
|
|
|
|
|
|
|
|
|
|
// 在构造器上无需加 @Autowired(Spring Boot 下可省略)
|
|
|
|
|
public OrderService(PaymentClient paymentClient) {
|
|
|
|
|
this.paymentClient = paymentClient;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2)Lombok `@RequiredArgsConstructor`
|
|
|
|
|
|
|
|
|
|
用 Lombok 自动为所有 `final` 字段生成构造器,进一步简化写法:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class OrderService {
|
|
|
|
|
|
|
|
|
|
private final PaymentClient paymentClient; // Lombok 会在编译期生成构造器
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-06 19:28:06 +08:00
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
### 配置文件
|
|
|
|
|
|
|
|
|
|
#### 配置优先级
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
在SpringBoot项目当中,常见的属性配置方式有5种, 3种配置文件,加上2种外部属性的配置(Java系统属性、命令行参数)。优先级(从低到高):
|
|
|
|
|
|
|
|
|
|
- application.yaml(忽略)
|
|
|
|
|
- application.yml
|
|
|
|
|
- application.properties
|
|
|
|
|
- java系统属性(-Dxxx=xxx)
|
|
|
|
|
- 命令行参数(--xxx=xxx)
|
|
|
|
|
|
|
|
|
|
在 Spring Boot 项目中,通常使用的是 **application.yml** 或 **application.properties** 文件,这些文件通常放在项目的 **src/main/resources** 目录下。
|
|
|
|
|
|
|
|
|
|
如果项目已经打包上线了,这个时候我们又如何来设置Java系统属性和命令行参数呢?
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
java -Dserver.port=9000 -jar XXXXX.jar --server.port=10010
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在这个例子中,由于命令行参数的优先级高于 Java 系统属性,最终生效的 `server.port` 是 **10010**。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
#### **properties**
|
|
|
|
|
|
|
|
|
|
位置:`src/main/resources/application.properties`
|
|
|
|
|
|
|
|
|
|
将配置信息写在application.properties,用注解@Value获取配置文件中的数据
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
`@Value("${aliyun.oss.endpoint}")`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-06 19:28:06 +08:00
|
|
|
|
#### yml配置文件(推荐!!!)
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
位置:`src/main/resources/application.yml`
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
了解下yml配置文件的基本语法:
|
|
|
|
|
|
|
|
|
|
- 大小写敏感
|
|
|
|
|
- **数据前边必须有空格**,作为分隔符
|
|
|
|
|
- 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(idea中会自动将Tab转换为空格)
|
|
|
|
|
- 缩进的空格数目不重要,只要**相同层级的元素左侧对齐**即可
|
|
|
|
|
- `#`表示注释,从这个字符一直到行尾,都会被解析器忽略
|
|
|
|
|
|
|
|
|
|
对象/map集合
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
user:
|
|
|
|
|
name: zhangsan
|
|
|
|
|
detail:
|
|
|
|
|
age: 18
|
|
|
|
|
password: "123456"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
数组/List/Set集合
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
hobby:
|
|
|
|
|
- java
|
|
|
|
|
- game
|
|
|
|
|
- sport
|
|
|
|
|
|
|
|
|
|
//获取示例
|
|
|
|
|
@Value("${hobby}")
|
|
|
|
|
private List<String> hobby;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
以上获取配置文件中的属性值,需要通过@Value注解,有时过于繁琐!!!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-06 19:28:06 +08:00
|
|
|
|
#### @ConfigurationProperties
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
是用来**将外部配置(如 `application.properties` / `application.yml`)映射到一个 POJO** 上的。
|
|
|
|
|
|
|
|
|
|
在 **Spring Boot** 中,根据 **驼峰命名转换**规则,自动将 `YAML` 配置文件中的 **键名**(例如 `user-token-name` `user_token_name`)映射到 **Java 类中的属性**(例如 `userTokenName`)。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Data
|
|
|
|
|
@Component
|
|
|
|
|
@ConfigurationProperties(prefix = "aliyun.oss")
|
|
|
|
|
public class AliOssProperties {
|
|
|
|
|
private String endpoint;
|
|
|
|
|
private String accessKeyId;
|
|
|
|
|
private String accessKeySecret;
|
|
|
|
|
private String bucketName;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Spring提供的简化方式套路:
|
|
|
|
|
|
|
|
|
|
1. 需要创建一个实现类,且实体类中的**属性名**和配置文件当中**key**的名字必须要**一致**
|
|
|
|
|
|
|
|
|
|
> 比如:配置文件当中叫endpoints,实体类当中的属性也得叫endpoints,另外实体类当中的属性还需要提供 getter / setter方法 **==》@Data**
|
|
|
|
|
|
|
|
|
|
2. 需要将实体类交给Spring的IOC容器管理,成为IOC容器当中的bean对象 **==>@Component**
|
|
|
|
|
|
|
|
|
|
3. 在实体类上添加`@ConfigurationProperties`注解,并通过**perfix属性**来指定配置参数项的前缀
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
2025-05-24 21:15:55 +08:00
|
|
|
|
4. (可选)引入依赖pom.xml (自动生成配置元数据,让 IDE **能识别并补全**你在 `application.properties/yml` 中的自定义配置项,提高开发体验,不加不影响运行!)
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>org.springframework.boot</groupId>
|
|
|
|
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
|
|
|
|
</dependency>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
### Bean 的获取和管理
|
|
|
|
|
|
|
|
|
|
#### 获取Bean
|
|
|
|
|
|
|
|
|
|
**1.自动装配(@Autowired)**
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Service
|
|
|
|
|
public class MyService {
|
|
|
|
|
@Autowired
|
|
|
|
|
private MyRepository myRepository; // 自动注入 MyRepository Bean
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**2.手动获取(ApplicationContext)**
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- `@Autowired` 自动将 Spring 创建的 `ApplicationContext` 注入到 `applicationContext` 字段中,
|
|
|
|
|
|
|
|
|
|
- 再通过 `applicationContext.getBean(...)` 拿到其他 Bean
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
Spring 会默认采用类名并将首字母小写作为 Bean 的名称。例如,类名为 `DeptController` 的组件默认名称就是 `deptController`。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@RunWith(SpringRunner.class)
|
|
|
|
|
@SpringBootTest
|
|
|
|
|
public class SpringbootWebConfig2ApplicationTests {
|
|
|
|
|
@Autowired
|
|
|
|
|
private ApplicationContext applicationContext; // IoC 容器
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testGetBean() {
|
|
|
|
|
// 根据 Bean 名称获取
|
|
|
|
|
DeptController bean = (DeptController) applicationContext.getBean("deptController");
|
|
|
|
|
System.out.println(bean);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
默认情况下,Spring 在容器启动时会创建所有单例 Bean(饿汉模式);使用 `@Lazy` 注解则可实现延迟加载(懒汉模式)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### bean的作用域
|
|
|
|
|
|
|
|
|
|
| **作用域** | **说明** |
|
|
|
|
|
| ---------- | -------------------------------------------------- |
|
|
|
|
|
| singleton | 容器内同名称的bean**只有一个实例**(单例)(默认) |
|
|
|
|
|
| prototype | 每次使用该bean时会创建新的实例(非单例) |
|
|
|
|
|
|
|
|
|
|
在设计单例类时,通常要求它们是**无状态的**,不仅要确保成员变量不可变,还需要确保成员方法不会对共享的、可变的状态进行不受控制的修改,从而实现整体的线程安全。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Service
|
|
|
|
|
public class CalculationService {
|
|
|
|
|
// 不可变的成员变量
|
|
|
|
|
private final double factor = 2.0;
|
|
|
|
|
|
|
|
|
|
// 成员方法仅依赖方法参数和不可变成员变量
|
|
|
|
|
public double multiply(double value) {
|
|
|
|
|
return value * factor;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**更改作用域方法:**
|
|
|
|
|
|
|
|
|
|
在bean类上加注解`@Scope("prototype")`(或其他作用域标识)即可。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
#### 第三方 Bean配置
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- 如果要管理的bean对象**来自于第三方**(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到**@Bean**注解。
|
2025-03-26 18:16:04 +08:00
|
|
|
|
- 如果需要定义第三方Bean时, 通常会单独定义一个**配置类**
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Configuration // 配置类
|
|
|
|
|
public class CommonConfig {
|
|
|
|
|
// 定义第三方 Bean,并交给 IoC 容器管理
|
2025-06-06 19:28:06 +08:00
|
|
|
|
@Bean
|
2025-03-26 18:16:04 +08:00
|
|
|
|
public SAXReader reader(DeptService deptService) {
|
|
|
|
|
System.out.println(deptService);
|
|
|
|
|
return new SAXReader();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
在应用启动时,Spring 会调用配置类中标注 `@Bean` 的方法,将方法**返回值注册**为容器中的 Bean 对象。
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
2025-06-06 19:28:06 +08:00
|
|
|
|
默认情况下,该 Bean 的名称就是**该方法的名字**。本例 Bean 名称默认就是 `"reader"`。Bean的类型就是返回值的类型,这里是`SAXReader`。
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
使用:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Service
|
|
|
|
|
public class XmlProcessingService {
|
|
|
|
|
|
|
|
|
|
// 按类型注入
|
|
|
|
|
@Autowired
|
2025-04-16 18:02:24 +08:00
|
|
|
|
private SAXReader reader; //方法的名字!!
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
public void parse(String xmlPath) throws DocumentException {
|
|
|
|
|
Document doc = reader.read(new File(xmlPath));
|
|
|
|
|
// ... 处理 Document ...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
### SpirngBoot原理
|
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
如果我们直接基于Spring框架进行项目的开发,会比较繁琐。SpringBoot框架之所以使用起来更简单更快捷,是因为SpringBoot框架底层提供了两个非常重要的功能:一个是起步依赖,一个是自动配置。
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
#### **起步依赖**
|
|
|
|
|
|
|
|
|
|
Spring Boot 只需要引入一个起步依赖(例如 `springboot-starter-web`)就能满足项目开发需求。这是因为:
|
|
|
|
|
|
|
|
|
|
- **Maven 依赖传递:**
|
|
|
|
|
起步依赖内部已经包含了开发所需的常见依赖(如 JSON 解析、Web、WebMVC、Tomcat 等),无需开发者手动引入其它依赖。
|
|
|
|
|
- **结论:**
|
|
|
|
|
起步依赖的核心原理就是 Maven 的依赖传递机制。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### **自动配置**
|
|
|
|
|
|
|
|
|
|
Spring Boot 会自动扫描启动类**所在包及其子包中**的所有带有组件注解(如 `@Component`, `@Service`, `@Repository`, `@Controller`, `@Mapper` 等)的类并加载到IOC容器中。
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是:
|
|
|
|
|
|
|
|
|
|
- @SpringBootConfiguration
|
2025-04-16 18:02:24 +08:00
|
|
|
|
- 声明当前类是一个配置类,等价于 `@Configuration`又与之区分
|
2025-03-26 18:16:04 +08:00
|
|
|
|
- @**ComponentScan**
|
2025-04-16 18:02:24 +08:00
|
|
|
|
- 进行组件扫描。如果你的项目有server pojo common模块,启动类在`com.your.package.server`下,那么只会默认扫描`com.your.package`及其子包。
|
|
|
|
|
- `@ComponentScan({"com.your.package.server", "com.your.package.common"})`可以显示指定扫描的包路径。
|
|
|
|
|
- @**EnableAutoConfiguration**(自动配置核心注解,下节详解)
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
**自动配置的效果:**
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|

|
|
|
|
|
|
|
|
|
|
在IOC容器中除了我们自己定义的bean以外,还有很多配置类,这些配置类都是SpringBoot在启动的时候加载进来的配置类。这些配置类加载进来之后,它也会生成很多的bean对象。
|
|
|
|
|
|
|
|
|
|
当我们想要使用这些配置类中生成的bean对象时,可以使用@Autowired就自动注入了。
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**如何让第三方bean以及配置类生效?**
|
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
如果配置类(如 `CommonConfig`)不在 Spring Boot 启动类的扫描路径内(即不在启动类所在包或其子包下):
|
|
|
|
|
|
|
|
|
|
1.`@ComponentScan`添加包扫描路径,适合批量导入(繁琐、性能低)
|
|
|
|
|
|
|
|
|
|
2.通过 `@Import` 手动导入该配置类。适合精确导入,如:
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
com
|
|
|
|
|
└── example
|
|
|
|
|
└── SpringBootApplication.java // 启动类
|
|
|
|
|
src
|
|
|
|
|
└── com
|
|
|
|
|
└── config
|
|
|
|
|
└── CommonConfig.java // 配置类
|
|
|
|
|
```
|
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
借助 `@Import` 注解,我们可以将外部的普通类、配置类或实现了 `ImportSelector` 的类**显式导入**到 Spring 容器中。 也就是这些类会加载到IOC容器中。
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
|
|
|
|
|
**1.使用@Import导入普通类:**
|
|
|
|
|
|
|
|
|
|
如果某个普通类(如 `TokenParser`)没有 `@Component` 注解标识,也可以通过 `@Import` 导入它,使其成为 Spring 管理的 Bean。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
// TokenParser 类没有 @Component 注解
|
|
|
|
|
public class TokenParser {
|
|
|
|
|
public void parse(){
|
|
|
|
|
System.out.println("TokenParser ... parse ...");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在启动类上使用 `@Import` 导入:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中
|
|
|
|
|
@SpringBootApplication
|
|
|
|
|
public class SpringbootWebConfig2Application {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
SpringApplication.run(SpringbootWebConfig2Application.class, args);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**2.使用@Import导入配置类:**
|
|
|
|
|
|
|
|
|
|
配置类中可以定义多个 Bean,通过 `@Configuration` 和 `@Bean` 注解实现集中管理。
|
|
|
|
|
|
|
|
|
|
~~~java
|
|
|
|
|
@Configuration
|
|
|
|
|
public class HeaderConfig {
|
|
|
|
|
@Bean
|
|
|
|
|
public HeaderParser headerParser(){
|
|
|
|
|
return new HeaderParser();
|
|
|
|
|
}
|
|
|
|
|
@Bean
|
|
|
|
|
public HeaderGenerator headerGenerator(){
|
|
|
|
|
return new HeaderGenerator();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
~~~
|
|
|
|
|
|
|
|
|
|
启动类导入配置类:
|
|
|
|
|
|
|
|
|
|
~~~java
|
|
|
|
|
@Import(HeaderConfig.class) //导入配置类
|
|
|
|
|
@SpringBootApplication
|
|
|
|
|
public class SpringbootWebConfig2Application {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
SpringApplication.run(SpringbootWebConfig2Application.class, args);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
~~~
|
|
|
|
|
|
|
|
|
|
**3.使用第三方依赖@EnableXxxx 注解**
|
|
|
|
|
|
2025-03-29 11:33:34 +08:00
|
|
|
|
如果第三方依赖没有提供自动配置支持,
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
常见方案是第三方依赖提供一个 `@EnableXxxx` 注解,这个注解内部封装了 `@Import`,通过它可以一次性导入多个配置或 Bean。
|
|
|
|
|
|
|
|
|
|
~~~java
|
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
|
|
|
@Target(ElementType.TYPE)
|
|
|
|
|
@Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类
|
|
|
|
|
public @interface EnableHeaderConfig {
|
|
|
|
|
}
|
|
|
|
|
~~~
|
|
|
|
|
|
|
|
|
|
在应用启动类上添加第三方依赖提供的 `@EnableHeaderConfig` 注解,即可导入相关的配置和 Bean。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解
|
|
|
|
|
@SpringBootApplication
|
|
|
|
|
public class SpringbootWebConfig2Application {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
SpringApplication.run(SpringbootWebConfig2Application.class, args);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
推荐第三种方式!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
#### @EnableAutoConfiguration
|
|
|
|
|
|
|
|
|
|
**导入自动配置类**
|
|
|
|
|
|
|
|
|
|
- 通过元注解 `@Import(AutoConfigurationImportSelector.class)`,在启动时读取所有 JAR 包中 `META‑INF/spring.factories` 下 `EnableAutoConfiguration` 对应的自动配置类列表。
|
|
|
|
|
- 将这些配置类当作 `@Configuration` 导入到 Spring 容器中。
|
|
|
|
|
|
|
|
|
|
**按条件注册 Bean**
|
|
|
|
|
|
|
|
|
|
- 自动配置类内部使用多种条件注解(如 `@ConditionalOnClass`、`@ConditionalOnMissingBean`、`@ConditionalOnProperty` 等)。
|
|
|
|
|
- Spring Boot 会检查当前类路径、配置属性和已有 Bean,**仅在满足所有条件时**,才执行对应的 `@Bean` 方法,将组件注入 IOC 容器。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
`@ComponentScan` 用于发现和加载应用自身的组件;
|
|
|
|
|
|
|
|
|
|
`@EnableAutoConfiguration` 则负责加载 Spring Boot 提供的“开箱即用”配置。如:
|
|
|
|
|
|
|
|
|
|
- `DataSourceAutoConfiguration` 检测到常见的 JDBC 驱动(如 HikariCP、Tomcat JDBC)和配置属性(`spring.datasource.*`)时,自动创建并配置 `DataSource`。、
|
|
|
|
|
- `WebMvcAutoConfiguration`自动配置 Spring MVC 的核心组件,并启用默认的静态资源映射、消息转换器(Jackson JSON)等。但遇到用户自定义的 MVC 支持配置(如继承 `WebMvcConfigurationSupport` )时会“失效”(Back Off)因为其内部有个注解:`@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)`,一旦容器内有xx类型注解,默认配置自动失效。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
## 常见的注解!!
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
1. `@RequestMapping("/jsonParam")`:可以用于**控制器级别**,也可以用于**方法级别**。
|
|
|
|
|
用于方法:HTTP 请求路径为 `/jsonParam` 的请求将调用该方法。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@RequestMapping("/jsonParam")
|
|
|
|
|
public String jsonParam(@RequestBody User user){
|
|
|
|
|
System.out.println(user);
|
|
|
|
|
return "OK";
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
用于控制器: 所有方法的映射路径都会以这个前缀开始。
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-26 09:31:51 +08:00
|
|
|
|
@RestController
|
|
|
|
|
@RequestMapping("/depts")
|
|
|
|
|
public class DeptController {
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
@GetMapping("/{id}")
|
|
|
|
|
public Dept getDept(@PathVariable Long id) {
|
|
|
|
|
// 实现获取部门逻辑
|
|
|
|
|
}
|
|
|
|
|
@PostMapping
|
|
|
|
|
public void createDept(@RequestBody Dept dept) {
|
|
|
|
|
// 实现新增部门逻辑
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-29 11:33:34 +08:00
|
|
|
|
1. `@RequestBody`:这是一个**方法参数级别**的注解,用于告诉Spring框架将请求体的内容解析为指定的**Java对象**。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
2. `@RestController`:这是一个类级别的注解,它告诉Spring框架这个类是一个控制器(Controller),并且处理HTTP请求并返回响应数据。与 `@Controller` 注解相比,`@RestController` 注解还会自动将控制器方法返回的数据转换为 JSON 格式,并写入到HTTP响应中,得益于`@ResponseBody` 。
|
|
|
|
|
`@RestController = @Controller + @ResponseBody`
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-29 11:33:34 +08:00
|
|
|
|
4. `@PathVariable` 注解用于将路径参数 `{id}` 的值绑定到方法的参数 `id` 上。当请求的路径是 "/path/123" 时,`@PathVariable` 会将路径中的 "123" 值绑定到方法的参数 `id` 上。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 17:35:22 +08:00
|
|
|
|
public String pathParam(@PathVariable Integer id) {
|
|
|
|
|
System.out.println(id);
|
|
|
|
|
return "OK";
|
2025-03-18 12:46:59 +08:00
|
|
|
|
}
|
2025-03-18 17:35:22 +08:00
|
|
|
|
|
2025-03-29 11:33:34 +08:00
|
|
|
|
//参数名与路径名不同
|
2025-03-18 17:35:22 +08:00
|
|
|
|
@GetMapping("/{id}")
|
2025-03-29 11:33:34 +08:00
|
|
|
|
public ResponseEntity<User> getUserById(@PathVariable("id") Long userId) {
|
|
|
|
|
}
|
2025-03-18 17:35:22 +08:00
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-05-24 21:15:55 +08:00
|
|
|
|
5. `@RequestParam`,
|
|
|
|
|
1)如果方法的参数名与请求参数名不同,需要在 `@RequestParam` 注解中指定请求参数的名字。
|
2025-03-29 11:33:34 +08:00
|
|
|
|
类似`@PathVariable`,可以指定参数名称。
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 17:35:22 +08:00
|
|
|
|
@RequestMapping("/example")
|
|
|
|
|
public String exampleMethod(@RequestParam String name, @RequestParam("age") int userAge) {
|
|
|
|
|
// 在方法内部使用获取到的参数值进行处理
|
|
|
|
|
System.out.println("Name: " + name);
|
|
|
|
|
System.out.println("Age: " + userAge);
|
|
|
|
|
return "OK";
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-05-24 21:15:55 +08:00
|
|
|
|
2)还可以设置默认值
|
2025-04-03 17:42:27 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@RequestMapping("/greet")
|
|
|
|
|
public String greet(@RequestParam(defaultValue = "Guest") String name) {
|
|
|
|
|
return "Hello, " + name;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-05-24 21:15:55 +08:00
|
|
|
|
3)如果既改请求参数名字,又要设置默认值
|
2025-04-03 17:42:27 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@RequestMapping("/greet")
|
|
|
|
|
public String greet(@RequestParam(value = "age", defaultValue = "25") int userAge) {
|
|
|
|
|
return "Age: " + userAge;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-05-24 21:15:55 +08:00
|
|
|
|
4)如果方法参数是简单类型(`int`/`Integer`、`String`、`boolean`/`Boolean` 等及它们的一维数组),那么无需使用@RequestParam,**如果是Collection集合类型,必须使用**。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-29 11:33:34 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
5. 控制反转与依赖注入:
|
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
`@Component`、`@Service`、`@Repository` 用于标识 bean 并让容器管理它们,从而实现 IoC。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
`@Autowired`、`@Configuration`、`@Bean` 用于实现 DI,通过容器自动装配或配置 bean 的依赖。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
7. **数据库相关。**
|
|
|
|
|
`@Mapper`注解:表示是mybatis中的Mapper接口,程序运行时,框架会自动生成接口的实现类对象(代理对象),**并交给Spring的IOC容器管理**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
`@Select`注解:代表的就是select查询,用于书写select查询语句
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
7. `@SpringBootTest`:它会启动 Spring 应用程序上下文,并在测试期间模拟运行整个 Spring Boot 应用程序。这意味着你可以在集成测试中使用 Spring 的各种功能,例如**自动装配、依赖注入、配置加载**等。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 09:31:51 +08:00
|
|
|
|
9. lombok的相关注解。非常实用的工具库。
|
2025-03-18 17:35:22 +08:00
|
|
|
|
|
2025-04-02 18:28:46 +08:00
|
|
|
|
- 在pom.xml文件中引入依赖
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
<!-- 在springboot的父工程中,已经集成了lombok并指定了版本号,故当前引入依赖时不需要指定version -->
|
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>org.projectlombok</groupId>
|
|
|
|
|
<artifactId>lombok</artifactId>
|
|
|
|
|
</dependency>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 在实体类上添加以下注解(加粗为常用)
|
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
| **注解** | **作用** |
|
|
|
|
|
| ----------------------- | ------------------------------------------------------------ |
|
|
|
|
|
| @Getter/@Setter | 为所有的属性提供get/set方法 |
|
|
|
|
|
| @ToString | 会给类自动生成易阅读的 toString 方法 |
|
|
|
|
|
| @EqualsAndHashCode | 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法 |
|
|
|
|
|
| **@Data** | **提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode)** |
|
|
|
|
|
| **@NoArgsConstructor** | 为实体类生成无参的构造器方法 |
|
|
|
|
|
| **@AllArgsConstructor** | 为实体类生成除了static修饰的字段之外带有各参数的构造器方法。 |
|
|
|
|
|
| **@Slf4j** | 可以log.info("输出日志信息"); |
|
2025-03-18 17:35:22 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 17:35:22 +08:00
|
|
|
|
//equals 方法用于比较两个对象的内容是否相同
|
|
|
|
|
Address addr1 = new Address("SomeProvince", "SomeCity");
|
|
|
|
|
Address addr2 = new Address("SomeProvince", "SomeCity");
|
|
|
|
|
System.out.println(addr1.equals(addr2)); // 输出 true
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
|
|
|
|
|
log:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
log.info("应用启动成功");
|
|
|
|
|
Long empId = 12L;
|
|
|
|
|
log.info("当前员工id:{}", empId); //带占位符,推荐!
|
|
|
|
|
log.info("当前员工id:" + empId); //不错,但不推荐
|
|
|
|
|
log.info("当前员工id:", empId); //错误的!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
10. `@Test`,Junit测试单元,可在测试类中定义测试函数,一次性执行所有@Test注解下的函数,不用写main方法
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
11. `@Override`,当一个方法在子类中覆盖(重写)了父类中的同名方法时,为了确保正确性,可以使用 `@Override` 注解来标记这个方法,这样编译器就能够帮助检查是否正确地重写了父类的方法。
|
2025-03-26 09:31:51 +08:00
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
12. `@DateTimeFormat`将日期转化为指定的格式。Spring会尝试将接收到的**字符串参数**转换为控制器方法参数的相应类型。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
```java
|
|
|
|
|
@RestController
|
|
|
|
|
public class DateController {
|
|
|
|
|
|
|
|
|
|
// 例如:请求 URL 为 /search?begin=2025-03-28
|
|
|
|
|
@GetMapping("/search")
|
|
|
|
|
public String search(@RequestParam("begin") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin) {
|
|
|
|
|
// 此时 begin 已经是 LocalDate 类型,可以直接使用
|
|
|
|
|
return "接收到的日期是: " + begin;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
12. @RestControllerAdvice= @ControllerAdvice + @ResponseBody。加上这个注解就代表我们定义了一个全局异常处理器,而且处理异常的方法返回值会转换为json后再响应给前端
|
|
|
|
|
|
2025-03-30 20:47:00 +08:00
|
|
|
|
```java
|
|
|
|
|
@RestControllerAdvice
|
|
|
|
|
public class GlobalExceptionHandler {
|
|
|
|
|
@ExceptionHandler(Exception.class)
|
|
|
|
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
|
|
|
public String handleException(Exception ex) {
|
|
|
|
|
// 返回错误提示或错误详情
|
|
|
|
|
return "系统发生异常:" + ex.getMessage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
12. `@Configuration`和`@Bean`配合使用,可以对第三方bean进行**集中**的配置管理,依赖注入!!`@Bean`用于方法上。加了`@Configuration`,当Spring Boot应用**启动时,它会执行**一系列的自动配置步骤。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
12. `@ComponentScan`指定了Spring应该在哪些包下搜索带有`@Component`、`@Service`、`@Repository`、`@Controller`等注解的类,以便将这些类自动注册为Spring容器管理的Bean.`@SpringBootApplication`它是一个便利的注解,组合了`@Configuration`、`@EnableAutoConfiguration`和`@ComponentScan`注解。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
## 登录校验
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
### 会话技术
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
会话是和浏览器关联的,当有三个浏览器客户端和服务器建立了连接时,就会有三个会话。同一个浏览器在未关闭之前请求了多次服务器,这多次请求是属于同一个会话。比如:1、2、3这三个请求都是属于同一个会话。当我们关闭浏览器之后,这次会话就结束了。而如果我们是直接把web服务器关了,那么所有的会话就都结束了。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
会话跟踪技术有三种:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
1. Cookie(客户端会话跟踪技术)
|
|
|
|
|
2. Session(服务端会话跟踪技术)
|
|
|
|
|
3. 令牌技术
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**Cookie**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**原理**:会话数据**存储在客户端浏览器**中,通过浏览器自动管理。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- 优点:HTTP协议中支持的技术(像Set-Cookie 响应头的解析以及 Cookie 请求头数据的携带,都是浏览器自动进行的,是无需我们手动操作的)
|
|
|
|
|
- 缺点:
|
|
|
|
|
- 移动端APP(Android、IOS)中无法使用Cookie
|
|
|
|
|
- 不安全,用户可以自己禁用Cookie
|
|
|
|
|
- Cookie不能跨域传递
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**Session**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**原理**:**服务端存储**会话数据(如内存、Redis),客户端**只保存**会话 ID。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
Session 底层是基于Cookie实现的会话跟踪,因此Cookie的缺点他也有。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- 优点:Session是存储在服务端的,安全。会话数据存在客户端有篡改的风险。
|
|
|
|
|
- 缺点:
|
|
|
|
|
- 在分布式服务器集群环境下,Session 无法自动共享
|
|
|
|
|
- 如果客户端禁用 Cookie,Session 会失效。
|
|
|
|
|
- 需要在服务器端存储会话信息,可能带来性能压力,尤其是在高并发环境下。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**流程解析**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
1.**当用户登录时**,客户端(浏览器)向服务器发送请求(如用户名和密码)。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
服务器验证用户身份,如果身份验证成功,服务器会生成一个 **唯一标识符**(例如 `userId` 或 `authToken`),并将其存储在 **Cookie** 中。服务器会通过 **`Set-Cookie`** HTTP 响应头将这个信息发送到浏览器:如:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
```text
|
|
|
|
|
Set-Cookie: userId=12345; Path=/; HttpOnly; Secure; Max-Age=3600;
|
2025-03-18 12:46:59 +08:00
|
|
|
|
```
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`userId=12345` 是服务器返回的标识符。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`Path=/` 表示此 Cookie 对整个网站有效。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`HttpOnly` 限制客户端 JavaScript 访问该 Cookie,提高安全性。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`Secure` 指示该 Cookie 仅通过 HTTPS 协议传输。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`Max-Age=3600` 设置 Cookie 的有效期为一小时。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
2.**浏览器存储 Cookie**:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- 浏览器收到 `Set-Cookie` 响应头后,会自动将 **`userId`** 存储在客户端的 Cookie 中。
|
|
|
|
|
- **`userId`** 会在 **本地存储**,并在浏览器的后续请求中自动携带。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
3.**后续请求发送 Cookie**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- 当浏览器再次向服务器发送请求时,它会自动在 HTTP 请求头中附带之前存储的 Cookie。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
4.**服务器识别用户**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- 服务器通过读取请求中的 `Cookie`,获取 **`userId`**(或其他标识符),然后可以从数据库或缓存中获取对应的用户信息。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**令牌(推荐)**
|
|
|
|
|
|
|
|
|
|
- 优点:
|
|
|
|
|
- 支持PC端、移动端
|
|
|
|
|
- 解决集群环境下的认证问题
|
|
|
|
|
- 减轻服务器的存储压力(无需在服务器端存储)
|
|
|
|
|
- 缺点:需要自己实现(包括令牌的生成、令牌的传递、令牌的校验)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
### 跨域问题
|
|
|
|
|
|
|
|
|
|
**跨域问题**指的是在浏览器中,**一个网页**试图去访问**另一个域**下的资源时,浏览器出于安全考虑,默认会阻止这种操作。这是浏览器的同源策略(Same-Origin Policy)导致的行为。
|
|
|
|
|
|
|
|
|
|
**同源策略(Same-Origin Policy)**
|
|
|
|
|
|
|
|
|
|
同源策略是浏览器的一种安全机制,它要求:
|
|
|
|
|
|
|
|
|
|
- **协议**(如 `http`、`https`)
|
|
|
|
|
- **域名/IP**(如 `example.com`)
|
|
|
|
|
- **端口**(如 `80` 或 `443`)
|
|
|
|
|
|
|
|
|
|
这三者必须完全相同,才能被视为同源。
|
|
|
|
|
|
|
|
|
|
举例:
|
|
|
|
|
|
|
|
|
|
http://192.168.150.200/login.html ----------> https://192.168.150.200/login [协议不同,跨域]
|
|
|
|
|
|
|
|
|
|
http://192.168.150.200/login.html ----------> http://192.168.150.100/login [IP不同,跨域]
|
|
|
|
|
|
|
|
|
|
http://192.168.150.200/login.html ----------> http://192.168.150.200:8080/login [端口不同,跨域]
|
|
|
|
|
|
|
|
|
|
http://192.168.150.200/login.html ----------> http://192.168.150.200/login [不跨域]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**解决跨域问题的方法:**
|
|
|
|
|
|
|
|
|
|
**CORS**(Cross-Origin Resource Sharing)是解决跨域问题的标准机制。它允许服务器在响应头中加上特定的 **CORS** 头部信息,明确表示允许哪些外域访问其资源。
|
|
|
|
|
|
|
|
|
|
**服务器端配置**:服务器返回带有 `Access-Control-Allow-Origin` 头部的响应,告诉浏览器允许哪些域访问资源。
|
|
|
|
|
|
|
|
|
|
- `Access-Control-Allow-Origin: *`(表示允许所有域访问)
|
|
|
|
|
- `Access-Control-Allow-Origin: http://site1.com`(表示只允许 `http://site1.com` 访问)
|
|
|
|
|
|
|
|
|
|
**全局统一配置**
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
|
|
|
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
|
|
|
|
|
|
|
|
@Configuration
|
|
|
|
|
public class WebCorsConfig implements WebMvcConfigurer {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void addCorsMappings(CorsRegistry registry) {
|
|
|
|
|
registry.addMapping("/api/**") // 匹配所有 /api/** 路径
|
|
|
|
|
.allowedOrigins("http://allowed-domain.com") // 允许的域名
|
|
|
|
|
.allowedMethods("GET","POST","PUT","DELETE","OPTIONS")
|
|
|
|
|
.allowedHeaders("Content-Type","Authorization")
|
|
|
|
|
.allowCredentials(true) // 是否允许携带 Cookie
|
|
|
|
|
.maxAge(3600); // 预检请求缓存 1 小时
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2025-04-15 12:46:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Nginx解决方案**
|
|
|
|
|
|
|
|
|
|
统一域名入口:
|
|
|
|
|
前端和 API 均通过 Nginx 以相同的域名(例如 https://example.com)提供服务。前端发送 AJAX 请求时,目标也是该域名的地址,如 https://example.com/api,从而避免了跨域校验。
|
|
|
|
|
|
|
|
|
|
Nginx 作为中间代理:
|
|
|
|
|
Nginx 将特定路径(例如 /api/)的请求转发到后端服务器。对浏览器来说,请求和响应均来自同一域名,代理过程对浏览器透明。
|
|
|
|
|
|
|
|
|
|
“黑匣子”处理:
|
|
|
|
|
浏览器只与 Nginx 交互,不关心 Nginx 内部如何转发请求。无论后端位置如何,浏览器都认为响应源自统一域名,从而解决跨域问题。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**总结**
|
|
|
|
|
|
|
|
|
|
普通的跨域请求依然会送达服务器,**服务器并不主动拦截**;它只是通过响应头声明哪些来源被允许访问,而真正的拦截与安全检查,则**由浏览器**根据同源策略来完成。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
### JWT令牌
|
|
|
|
|
|
2025-05-20 19:32:40 +08:00
|
|
|
|

|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
| 特性 | Session | JWT(JSON Web Token) |
|
|
|
|
|
| -------- | ------------------------------------ | ----------------------------------------------- |
|
|
|
|
|
| 存储方式 | 服务端存储会话数据(如内存、Redis) | 客户端存储完整的令牌(通常在 Header 或 Cookie) |
|
|
|
|
|
| 标识方式 | 客户端持有一个 Session ID | 客户端持有一个自包含的 Token |
|
|
|
|
|
| 状态管理 | 有状态(Stateful),服务器要维护会话 | 无状态(Stateless),服务器不存会话 |
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### **生成和校验**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
引入依赖
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>io.jsonwebtoken</groupId>
|
|
|
|
|
<artifactId>jjwt</artifactId>
|
|
|
|
|
<version>0.9.1</version>
|
|
|
|
|
</dependency>
|
|
|
|
|
```
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
生成令牌与解析令牌:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
public class JwtUtils {
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
private static String signKey = "zy123";
|
|
|
|
|
private static Long expire = 43200000L; //单位毫秒 12小时
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成JWT令牌
|
|
|
|
|
* @param claims JWT第二部分负载 payload 中存储的内容
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String generateJwt(Map<String, Object> claims){
|
|
|
|
|
String jwt = Jwts.builder()
|
|
|
|
|
.addClaims(claims)
|
|
|
|
|
.signWith(SignatureAlgorithm.HS256, signKey)
|
|
|
|
|
.setExpiration(new Date(System.currentTimeMillis() + expire))
|
|
|
|
|
.compact();
|
|
|
|
|
return jwt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 解析JWT令牌
|
|
|
|
|
* @param jwt JWT令牌
|
|
|
|
|
* @return JWT第二部分负载 payload 中存储的内容
|
|
|
|
|
*/
|
|
|
|
|
public static Claims parseJWT(String jwt){
|
|
|
|
|
Claims claims = Jwts.parser()
|
|
|
|
|
.setSigningKey(signKey)
|
|
|
|
|
.parseClaimsJws(jwt)
|
|
|
|
|
.getBody();
|
|
|
|
|
return claims;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**令牌可以存储当前登录用户的信息:id、username等等,传入claims**
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`Object` 类型能够容纳字符串、数字等各种对象。
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
Map<String, Object> claims = new HashMap<>();
|
2025-04-09 17:23:21 +08:00
|
|
|
|
claims.put("id", emp.getId()); // 假设 emp.getId() 返回一个数字(如 Long 类型)
|
|
|
|
|
claims.put("name", e.getName()); // 假设 e.getName() 返回一个字符串
|
|
|
|
|
claims.put("username", e.getUsername()); // 假设 e.getUsername() 返回一个字符串
|
|
|
|
|
String jwt = JwtUtils.generateJwt(claims);
|
2025-03-18 12:46:59 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**解析令牌:**
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@Autowired
|
|
|
|
|
private HttpServletRequest request;
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
String jwt = request.getHeader("token");
|
|
|
|
|
Claims claims = JwtUtils.parseJWT(jwt); // 解析 JWT 令牌
|
|
|
|
|
|
|
|
|
|
// 获取存储的 id, name, username
|
|
|
|
|
Long id = (Long) claims.get("id"); // 如果 "id" 是 Long 类型
|
|
|
|
|
String name = (String) claims.get("name");
|
|
|
|
|
String username = (String) claims.get("username");
|
2025-03-18 12:46:59 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
#### **JWT 登录认证流程**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
1. 用户登录
|
2025-04-15 12:46:07 +08:00
|
|
|
|
用户发起登录请求,校验密码、登录成功后,生成 JWT 令牌,并将其返回给前端。
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
2. 前端存储令牌
|
|
|
|
|
前端接收到 JWT 令牌,**存储在浏览器中**(通常存储在 LocalStorage 或 Cookie 中)。
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
// 登录成功后,存储 JWT 令牌到 LocalStorage
|
|
|
|
|
const token = response.data.token; // 从响应中获取令牌
|
|
|
|
|
localStorage.setItem('token', token); // 存储到 LocalStorage
|
|
|
|
|
|
|
|
|
|
// 在后续请求中获取令牌并附加到请求头
|
|
|
|
|
const storedToken = localStorage.getItem('token');
|
|
|
|
|
fetch("https://your-api.com/protected-endpoint", {
|
|
|
|
|
method: "GET",
|
|
|
|
|
headers: {
|
|
|
|
|
"token": storedToken // 添加 token 到请求头
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.then(response => response.json())
|
|
|
|
|
.then(data => console.log(data))
|
|
|
|
|
.catch(error => console.log('Error:', error));
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
3. 请求带上令牌
|
|
|
|
|
后续的每次请求,前端将 JWT 令牌携带上。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
4. 服务端校验令牌
|
2025-04-15 12:46:07 +08:00
|
|
|
|
服务端接收到请求后,**拦截请求并检查是否携带令牌**。若没有令牌,拒绝访问;若令牌存在,校验令牌的**有效性**(包括有效期),若有效则放行,进行请求处理。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-16 18:02:24 +08:00
|
|
|
|
注意,使用APIFOX测试时,需要在headers中添加
|
|
|
|
|
|
|
|
|
|
{token:"jwt令牌..."}否则会无法通过拦截器。
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
### 拦截器(Interceptor)
|
|
|
|
|
|
|
|
|
|
在拦截器当中,我们通常也是做一些通用性的操作,比如:**我们可以通过拦截器来拦截前端发起的请求**,将登录校验的逻辑全部编写在拦截器当中。在校验的过程当中,如发现用户登录了(携带JWT令牌且是合法令牌),就可以直接放行,去访问spring当中的资源。如果校验时发现并没有登录或是非法令牌,就可以直接给前端响应未登录的错误信息。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 快速入门
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
1. 定义拦截器,**实现HandlerInterceptor接口**,并重写其所有方法
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
//自定义拦截器
|
|
|
|
|
@Component
|
2025-04-09 17:23:21 +08:00
|
|
|
|
public class JwtTokenUserInterceptor implements HandlerInterceptor {
|
2025-03-18 12:46:59 +08:00
|
|
|
|
//目标资源方法执行前执行。 返回true:放行 返回false:不放行
|
|
|
|
|
@Override
|
|
|
|
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|
|
|
|
System.out.println("preHandle .... ");
|
|
|
|
|
|
|
|
|
|
return true; //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 .... ");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
注意:
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`preHandle`方法:目标资源方法执行前执行。 **返回true:放行 返回false:不放行**
|
|
|
|
|
|
|
|
|
|
`postHandle`方法:目标资源方法执行后执行
|
|
|
|
|
|
|
|
|
|
`afterCompletion`方法:视图渲染完毕后执行,最后执行
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
2. **注册配置拦截器**,实现WebMvcConfigurer接口,并重写addInterceptors方法
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@Configuration
|
|
|
|
|
public class WebConfig implements WebMvcConfigurer {
|
|
|
|
|
|
|
|
|
|
//自定义的拦截器对象
|
|
|
|
|
@Autowired
|
2025-04-09 17:23:21 +08:00
|
|
|
|
private JwtTokenUserInterceptor jwtTokenUserInterceptor;
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
@Override
|
2025-04-09 17:23:21 +08:00
|
|
|
|
protected void addInterceptors(InterceptorRegistry registry) {
|
|
|
|
|
log.info("开始注册自定义拦截器...");
|
|
|
|
|
registry.addInterceptor(jwtTokenUserInterceptor)
|
|
|
|
|
.addPathPatterns("/user/**")
|
|
|
|
|
.excludePathPatterns("/user/user/login")
|
|
|
|
|
.excludePathPatterns("/user/shop/status");
|
2025-03-18 12:46:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-04-15 12:46:07 +08:00
|
|
|
|
**WebMvcConfigurer接口**:
|
|
|
|
|
|
|
|
|
|
**拦截器配置**
|
|
|
|
|
通过实现 `addInterceptors` 方法,可以添加自定义的拦截器,从而在请求进入处理之前或之后执行一些逻辑操作,如权限校验、日志记录等。
|
|
|
|
|
|
|
|
|
|
**静态资源映射**
|
|
|
|
|
通过 `addResourceHandlers` 方法,可以自定义静态资源(如 HTML、CSS、JavaScript)的映射路径,这对于使用前后端分离或者集成第三方文档工具(如 Swagger/Knife4j)非常有用。
|
|
|
|
|
|
|
|
|
|
**消息转换器扩展**
|
|
|
|
|
通过 `extendMessageConverters` 方法,可以在默认配置的基础上,追加自定义的 HTTP 消息转换器,如将 Java 对象转换为 JSON 格式。
|
|
|
|
|
|
|
|
|
|
**跨域配置**
|
|
|
|
|
使用 `addCorsMappings` 方法,可以灵活配置跨域资源共享(CORS)策略,方便前后端跨域请求。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
#### 拦截路径
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`addPathPatterns`指定拦截路径;
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
调用`excludePathPatterns("不拦截的路径")`方法,指定哪些资源不需要拦截。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
| 拦截路径 | 含义 | 举例 |
|
|
|
|
|
| --------- | -------------------- | --------------------------------------------------- |
|
|
|
|
|
| /* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
|
|
|
|
|
| /** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
|
|
|
|
|
| /depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
|
|
|
|
|
| /depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 |
|
|
|
|
|
|
|
|
|
|
#### 登录校验
|
|
|
|
|
|
|
|
|
|
主要在preHandle中写逻辑
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-04-09 17:23:21 +08:00
|
|
|
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|
|
|
|
//判断当前拦截到的是Controller的方法还是其他资源
|
|
|
|
|
if (!(handler instanceof HandlerMethod)) {
|
|
|
|
|
//当前拦截到的不是动态方法,直接放行
|
|
|
|
|
return true;
|
2025-03-18 12:46:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
//1、从请求头中获取令牌
|
|
|
|
|
String token = request.getHeader(jwtProperties.getUserTokenName());
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
//2、校验令牌
|
|
|
|
|
try {
|
|
|
|
|
log.info("jwt校验:{}", token);
|
|
|
|
|
Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
|
|
|
|
|
Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
|
|
|
|
|
log.info("当前用户id:", userId);
|
|
|
|
|
BaseContext.setCurrentId(userId);
|
|
|
|
|
//3、通过,放行
|
|
|
|
|
return true;
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
//4、不通过,响应401状态码
|
|
|
|
|
response.setStatus(401);
|
2025-03-18 12:46:59 +08:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
## 全局异常处理
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**当前问题:**如果程序因不知名原因报错,响应回来的数据是一个JSON格式的数据,但这种JSON格式的数据不符合开发规范当中所提到的统一响应结果Result吗,导致前端不能解析出响应的JSON数据。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
当我们没有做任何的异常处理时,我们三层架构处理异常的方案:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- Mapper接口在操作数据库的时候出错了,此时异常会往上抛(谁调用Mapper就抛给谁),会抛给service。
|
|
|
|
|
- service 中也存在异常了,会抛给controller。
|
|
|
|
|
- 而在controller当中,我们也没有做任何的异常处理,所以最终异常会再往上抛。最终抛给框架之后,框架就会返回一个JSON格式的数据,里面封装的就是错误的信息,但是框架返回的JSON格式的数据并不符合我们的开发规范。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**如何解决:**
|
|
|
|
|
|
|
|
|
|
- 方案一:在所有Controller的所有方法中进行try…catch处理
|
|
|
|
|
|
|
|
|
|
- 缺点:代码臃肿(不推荐)
|
|
|
|
|
|
|
|
|
|
- 方案二:全局异常处理器
|
|
|
|
|
|
|
|
|
|
- 好处:简单、优雅(推荐)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**全局异常处理**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
- 定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解**@RestControllerAdvice**,加上这个注解就代表我们定义了一个全局异常处理器。
|
|
|
|
|
- 在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解**@ExceptionHandler**。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@RestControllerAdvice
|
|
|
|
|
public class GlobalExceptionHandler {
|
2025-04-09 17:23:21 +08:00
|
|
|
|
//处理 RuntimeException 异常
|
|
|
|
|
@ExceptionHandler(RuntimeException.class)
|
|
|
|
|
public Result handleRuntimeException(RuntimeException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
return Result.error("系统错误,请稍后再试");
|
|
|
|
|
}
|
|
|
|
|
// 处理 NullPointerException 异常
|
|
|
|
|
@ExceptionHandler(NullPointerException.class)
|
|
|
|
|
public Result handleNullPointerException(NullPointerException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
return Result.error("空指针异常,请检查代码逻辑");
|
|
|
|
|
}
|
2025-03-18 12:46:59 +08:00
|
|
|
|
//处理异常
|
|
|
|
|
@ExceptionHandler(Exception.class) //指定能够处理的异常类型,Exception.class捕获所有异常
|
|
|
|
|
public Result ex(Exception e){
|
|
|
|
|
e.printStackTrace();//打印堆栈中的异常信息
|
|
|
|
|
//捕获到异常之后,响应一个标准的Result
|
|
|
|
|
return Result.error("对不起,操作失败,请联系管理员");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
模拟NullPointerException
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
String str = null;
|
|
|
|
|
// 调用 null 对象的方法会抛出 NullPointerException
|
|
|
|
|
System.out.println(str.length()); // 这里会抛出 NullPointerException
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
模拟RuntimeException
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
int res=10/0;
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 事务
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
### 问题分析:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-04-09 17:23:21 +08:00
|
|
|
|
@Slf4j
|
|
|
|
|
@Service
|
|
|
|
|
public class DeptServiceImpl implements DeptService {
|
|
|
|
|
@Autowired
|
|
|
|
|
private DeptMapper deptMapper;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private EmpMapper empMapper;
|
|
|
|
|
|
|
|
|
|
//根据部门id,删除部门信息及部门下的所有员工
|
|
|
|
|
@Override
|
|
|
|
|
public void delete(Integer id){
|
|
|
|
|
//根据部门id删除部门信息
|
|
|
|
|
deptMapper.deleteById(id);
|
|
|
|
|
|
|
|
|
|
//模拟:异常发生
|
|
|
|
|
int i = 1/0;
|
|
|
|
|
|
|
|
|
|
//删除部门下的所有员工信息
|
|
|
|
|
empMapper.deleteByDeptId(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-18 12:46:59 +08:00
|
|
|
|
```
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
即使程序运行抛出了异常,部门依然删除了,但是部门下的员工却没有删除,造成了数据的不一致。
|
|
|
|
|
|
|
|
|
|
因此,需要事务来控制这组操作,让这组操作同时成功或同时失败。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
### Transactional注解
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`@Transactional`作用:就是在当前这个方法执行开始之前来开启事务,**方法执行完毕之后提交事务**。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。一般会在**业务层(Service)**当中来控制事务。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`@Transactional`注解书写位置:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- 方法:当前方法交给spring进行事务管理
|
|
|
|
|
- 类:当前类中所有的方法都交由spring进行事务管理
|
|
|
|
|
- 接口:接口下所有的实现类当中所有的方法都交给spring 进行事务管理
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
`@Transactional`注解当中的两个常见的属性:
|
|
|
|
|
|
|
|
|
|
1. 异常回滚的属性:rollbackFor
|
|
|
|
|
2. 事务传播行为:propagation
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
默认情况下,只有出现**RuntimeException(运行时异常)**才会回滚事务。假如我们想让所有的异常都回滚,需要来配置@Transactional注解当中的**rollbackFor**属性,通过rollbackFor这个属性可以指定出现何种异常类型回滚事务。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Transactional(rollbackFor=Exception.class)
|
|
|
|
|
public void delete(Integer id){
|
|
|
|
|
//根据部门id删除部门信息
|
|
|
|
|
deptMapper.deleteById(id);
|
|
|
|
|
|
|
|
|
|
//模拟:异常发生
|
|
|
|
|
int num = id/0;
|
|
|
|
|
|
|
|
|
|
//删除部门下的所有员工信息
|
|
|
|
|
empMapper.deleteByDeptId(id);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
在`@Transactional`注解的后面指定一个属性**propagation**,通过 propagation 属性来指定事务的传播行为。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
什么是事务的传播行为呢?
|
|
|
|
|
|
|
|
|
|
- 就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。A方法运行的时候,首先会开启一个事务,在A方法当中又调用了B方法, B方法自身也具有事务,那么B方法在运行的时候,到底是加入到A方法的事务当中来,还是B方法在运行的时候新建一个事务?
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
| **属性值** | **含义** |
|
|
|
|
|
| ------------ | ------------------------------------------------------------ |
|
2025-04-09 17:23:21 +08:00
|
|
|
|
| REQUIRED | 【默认值】有父事务则加入,**父子有异常则一起回滚**;无父事务则创建新事务 |
|
2025-03-18 12:46:59 +08:00
|
|
|
|
| REQUIRES_NEW | 需要新事务,无论有无,**总是创建新事务** |
|
|
|
|
|
|
|
|
|
|
- REQUIRED :大部分情况下都是用该传播行为即可。
|
|
|
|
|
|
|
|
|
|
- REQUIRES_NEW :当我们**不希望事务之间相互影响**时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
```java
|
|
|
|
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Spring事务日志开关
|
|
|
|
|
|
|
|
|
|
```yml
|
|
|
|
|
logging:
|
|
|
|
|
level:
|
|
|
|
|
org.springframework.jdbc.support.JdbcTransactionManager: debug
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
当你设置 `debug` 级别日志时,Spring 会打印出关于事务的详细信息,例如事务的开启、提交、回滚以及数据库操作。
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### **总结**
|
|
|
|
|
|
|
|
|
|
当 **Service 层发生异常** 时,Spring 会按照以下顺序处理:
|
|
|
|
|
|
|
|
|
|
1. **事务的回滚**:如果 Service 层抛出了一个异常(如 `RuntimeException`),并且这个方法是 `@Transactional` 注解标注的,Spring 会在方法抛出异常时 **回滚事务**。Spring 事务管理器会自动触发回滚操作。
|
|
|
|
|
2. **异常传播到 Controller 层**:如果异常在 Service 层处理后未被捕获,它会传播到 Controller 层(即调用 `Service` 方法的地方)。
|
|
|
|
|
3. **全局异常处理器**:当异常传播到 Controller 层时,全局异常处理器(`@RestControllerAdvice` 或 `@ControllerAdvice`)会捕获并处理该异常,返回给前端一个标准的错误响应。
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## AOP
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**AOP**(Aspect-Oriented Programming,面向切面编程)是一种编程思想,旨在将横切关注点(如日志、性能监控等)从核心业务逻辑中分离出来。简单来说,AOP 是通过对特定方法的增强(如统计方法执行耗时)来实现**代码复用**和关注点分离。
|
|
|
|
|
|
|
|
|
|
**实现业务方法执行耗时统计的步骤**
|
|
|
|
|
|
|
|
|
|
1. 定义模板方法:将记录方法执行耗时的公共逻辑提取到**模板方法**中。
|
|
|
|
|
2. 记录开始时间:在方法执行前记录开始时间。
|
|
|
|
|
3. 执行原始业务方法:中间部分执行实际的业务方法。
|
|
|
|
|
4. 记录结束时间:在方法执行后记录结束时间,计算并输出执行时间。
|
|
|
|
|
|
|
|
|
|
通过 AOP,我们可以在不修改原有业务代码的情况下,完成对方法执行耗时的统计。
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 快速入门
|
|
|
|
|
|
|
|
|
|
**实现步骤:**
|
|
|
|
|
|
|
|
|
|
1. 导入依赖:在pom.xml中导入AOP的依赖
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
```xml
|
2025-03-18 12:46:59 +08:00
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>org.springframework.boot</groupId>
|
|
|
|
|
<artifactId>spring-boot-starter-aop</artifactId>
|
|
|
|
|
</dependency>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. 编写AOP程序:针对于特定方法根据业务需要进行编程
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@Component
|
|
|
|
|
@Aspect //当前类为切面类
|
|
|
|
|
@Slf4j
|
|
|
|
|
public class TimeAspect {
|
|
|
|
|
////第一个星号表示任意返回值,第二个星号表示类/接口,第三个星号表示所有方法。
|
2025-04-09 17:23:21 +08:00
|
|
|
|
@Around("execution(* edu.whut.zy123.service.*.*(..))")
|
2025-03-18 12:46:59 +08:00
|
|
|
|
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
|
|
|
|
|
//记录方法执行开始时间
|
|
|
|
|
long begin = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
//执行原始方法
|
|
|
|
|
Object result = pjp.proceed();
|
|
|
|
|
|
|
|
|
|
//记录方法执行结束时间
|
|
|
|
|
long end = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
//计算方法执行耗时,pjp.getSignature()获得函数名
|
|
|
|
|
log.info(pjp.getSignature()+"执行耗时: {}毫秒",end-begin);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
我们通过AOP入门程序完成了业务方法执行耗时的统计,那其实AOP的功能远不止于此,常见的应用场景如下:
|
|
|
|
|
|
|
|
|
|
- 记录系统的操作日志
|
|
|
|
|
- 权限控制
|
|
|
|
|
- 事务管理:我们前面所讲解的Spring事务管理,底层其实也是通过AOP来实现的,只要添加@Transactional注解之后,AOP程序自动会在原始方法运行前先来开启事务,在原始方法运行完毕之后提交或回滚事务
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 核心概念
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**1. 连接点:JoinPoint**,可以被AOP控制的**方法**,代表方法的执行位置
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**2. 通知:Advice**,指对目标方法的“增强”操作 (体现为额外的**代码**)
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**3. 切入点:PointCut**,是一个表达式,匹配连接点的条件,它指定了 **在目标方法的哪些位置插入通知**,比如在哪些方法调用之前、之后、或者哪些方法抛出异常时进行增强。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**4. 切面:Aspect**,通知与切入点的结合
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**5.目标对象:Target**,被 AOP 代理的对象,通知会作用到目标对象的对应方法上。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
示例:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
```java
|
|
|
|
|
@Slf4j
|
|
|
|
|
@Component
|
|
|
|
|
@Aspect
|
|
|
|
|
public class MyAspect {
|
|
|
|
|
@Before("execution(* edu.whut.zy123.service.MyService.doSomething(..))")
|
|
|
|
|
public void beforeMethod(JoinPoint joinPoint) {
|
|
|
|
|
// 连接点:目标方法执行位置
|
|
|
|
|
System.out.println("Before method: " + joinPoint.getSignature().getName());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`joinPoint` 代表的是 `doSomething()` 方法执行的**连接点**。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`beforeMethod()` 方法就是一个**前置通知**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`"execution(* com.example.service.MyService.doSomething(..))"`是**切入点**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`MyAspect`是**切面**。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
`com.example.service.MyService` 类的实例是**目标对象**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
### 通知类型
|
|
|
|
|
|
|
|
|
|
- **@Around**:环绕通知。此通知会在目标方法**前后都执行**。
|
|
|
|
|
- @Before:前置通知。此通知在目标方法**执行之前**执行。
|
|
|
|
|
- @After :后置通知。此通知在目标方法**执行后**执行,无论方法是否抛出异常。
|
|
|
|
|
- @AfterReturning : 返回后通知。此通知在目标方法**正常返回**后执行,**发生异常时不会执行**。
|
|
|
|
|
- @AfterThrowing : 异常后通知。此通知在目标方法**抛出异常后**执行。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
在使用通知时的注意事项:
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- **@Around** 通知必须调用 `ProceedingJoinPoint.proceed()` 才能执行目标方法,其他通知不需要。
|
|
|
|
|
- **@Around** 通知的返回值必须是 `Object` 类型,用于接收原始方法的返回值。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
**通知执行顺序**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
1. 默认情况下,不同切面类的通知执行顺序由**类名的字母顺序**决定。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
2. 可以通过 `@Order` 注解指定切面类的执行顺序,数字越小,优先级越高。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
例如:`@Order(1)` 表示该切面类的通知优先执行。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
```java
|
|
|
|
|
@Aspect
|
|
|
|
|
@Order(1) // 优先级1
|
|
|
|
|
@Component
|
|
|
|
|
public class AspectOne {
|
|
|
|
|
@Before("execution(* edu.whut.zy123.service.MyService.*(..))")
|
|
|
|
|
public void beforeMethod() {
|
|
|
|
|
System.out.println("AspectOne: Before method");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
```java
|
|
|
|
|
@Aspect
|
|
|
|
|
@Order(2) // 优先级2
|
|
|
|
|
@Component
|
|
|
|
|
public class AspectTwo {
|
|
|
|
|
@Before("execution(* edu.whut.zy123.service.MyService.*(..))")
|
|
|
|
|
public void beforeMethod() {
|
|
|
|
|
System.out.println("AspectTwo: Before method");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
如果调用 `MyService` 中的某个方法,AspectOne切面类中的通知会先执行。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
结论:目标方法前的通知方法,**Order小**的或者**类名的字母顺序在前**的先执行。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
目标方法后的通知方法,**Order小**的或者**类名的字母顺序在前**的后执行。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
相对于显式设置(Order)的通知,默认通知的优先级最低。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
### 切入点表达式
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
- 作用:主要用来**决定项目中的哪些方法需要加入通知**
|
|
|
|
|
|
|
|
|
|
- 常见形式:
|
|
|
|
|
|
|
|
|
|
1. execution(……):根据方法的签名来匹配
|
|
|
|
|
2. @annotation(……) :根据注解匹配
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
#### 公共表示@Pointcut
|
|
|
|
|
|
|
|
|
|
使用 `@Pointcut` 注解可以将切点表达式提取到一个独立的方法中,提高代码复用性和可维护性。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Aspect
|
|
|
|
|
@Component
|
|
|
|
|
public class LoggingAspect {
|
|
|
|
|
|
|
|
|
|
// 定义一个切点,匹配com.example.service包下 UserService 类的所有方法
|
|
|
|
|
@Pointcut("execution(public * com.example.service.UserService.*(..))")
|
|
|
|
|
public void userServiceMethods() {
|
|
|
|
|
// 该方法仅用来作为切点标识,无需实现任何内容
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 在目标方法执行前执行通知,引用上面的切点
|
|
|
|
|
@Before("userServiceMethods()")
|
|
|
|
|
public void beforeUserServiceMethods() {
|
|
|
|
|
System.out.println("【日志】即将执行 UserService 中的方法");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
#### execution
|
|
|
|
|
|
|
|
|
|
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
其中带`?`的表示可以省略的部分
|
|
|
|
|
|
|
|
|
|
- 访问修饰符:可省略(比如: public、protected)
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- 包名.类名.: 可省略,**但不建议**
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
- throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
示例:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-04-09 17:23:21 +08:00
|
|
|
|
//如果希望匹配 public void delete(Integer id)
|
|
|
|
|
@Before("execution(void edu.whut.zy123.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
|
|
|
|
|
|
|
|
|
|
//如果希望匹配 public void delete(int id)
|
|
|
|
|
@Before("execution(void edu.whut.zy123.service.impl.DeptServiceImpl.delete(int))")
|
2025-03-18 12:46:59 +08:00
|
|
|
|
```
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
在 Pointcut 表达式中,为了确保匹配准确,通常建议对非基本数据类型使用**全限定名**。这意味着,对于像 Integer 这样的类,最好写成 `java.lang.Integer`
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
可以使用**通配符**描述切入点
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
- `*` :**单个**独立的任意符号,可以通配任意返回值、包名、类名、方法名、**任意类型的一个参数**,也可以通配包、类、方法名的一部分
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
execution(* edu.*.service.*.update*(*))
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这里update后面的'星'即通配方法名的一部分,()中的'星'表示有且仅有一个任意参数
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
可以匹配:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
package edu.zju.service;
|
|
|
|
|
|
|
|
|
|
public class UserService {
|
|
|
|
|
public void updateUser(String username) {
|
|
|
|
|
// 方法实现
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- `..` :**多个**连续的任意符号,可以通配任意层级的包,或**任意类型、任意个数**的参数
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
execution(* com.example.service.UserService.*(..))
|
|
|
|
|
```
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### annotation
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
那么如果我们要匹配多个无规则的方法,比如:list()和 delete()这**两个**方法。我们可以借助于另一种切入点表达式annotation来描述这一类的切入点,从而来简化切入点表达式的书写。
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
实现步骤:
|
|
|
|
|
|
|
|
|
|
1. **新建anno包,在这个包下**编写自定义注解
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
import java.lang.annotation.ElementType;
|
|
|
|
|
import java.lang.annotation.Retention;
|
|
|
|
|
import java.lang.annotation.RetentionPolicy;
|
|
|
|
|
import java.lang.annotation.Target;
|
|
|
|
|
|
|
|
|
|
// 定义注解
|
|
|
|
|
@Retention(RetentionPolicy.RUNTIME) // 定义注解的生命周期
|
|
|
|
|
@Target(ElementType.METHOD) // 定义注解可以应用的Java元素类型
|
|
|
|
|
public @interface MyLog {
|
|
|
|
|
// 定义注解的元素(属性)
|
|
|
|
|
String description() default "This is a default description";
|
|
|
|
|
int value() default 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. 在业务类要做为连接点的**方法上添加**自定义注解
|
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-04-09 17:23:21 +08:00
|
|
|
|
@MyLog //自定义注解(表示:当前方法属于目标方法)
|
|
|
|
|
public void delete(Integer id) {
|
|
|
|
|
//1. 删除部门
|
|
|
|
|
deptMapper.delete(id);
|
|
|
|
|
}
|
2025-03-18 12:46:59 +08:00
|
|
|
|
```
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
3. AOP切面类上使用类似如下的切面表达式:
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-26 18:16:04 +08:00
|
|
|
|
```java
|
2025-03-18 12:46:59 +08:00
|
|
|
|
@Before("@annotation(edu.whut.anno.MyLog)")
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
### 连接点JoinPoint
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**执行**: ProceedingJoinPoint和 JoinPoint 都是调用 `proceed()` 就会执行被代理的方法
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
Object result = joinPoint.proceed();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**获取调用方法时传递的参数**,即使只有一个参数,也以数组形式返回:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
Object[] args = joinPoint.getArgs();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
**`getSignature()`**: 返回一个`Signature`类型的对象,这个对象包含了被拦截点的签名信息。在方法调用的上下文中,这包括了方法的名称、声明类型等信息。
|
|
|
|
|
|
|
|
|
|
- **方法名称**:可以通过调用`getName()`方法获得。
|
|
|
|
|
- **声明类型**:方法所在的类或接口的**完全限定名**,可以通过`getDeclaringTypeName()`方法获取。
|
|
|
|
|
- **返回类型**(对于方法签名):可以通过将`Signature`对象转换为更具体的`MethodSignature`类型,并调用`getReturnType()`方法获取。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## WEB开发总体图
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|