feat:更新文档架构
This commit is contained in:
221
docs/Web/SpringBoot/SpringAOP.md
Normal file
221
docs/Web/SpringBoot/SpringAOP.md
Normal file
@@ -0,0 +1,221 @@
|
||||
---
|
||||
title: Spring AOP简介
|
||||
date: 2025-12-25
|
||||
---
|
||||
|
||||
# 一、基本概念
|
||||
  AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,核心思想是**将与业务核心逻辑无关但又被多个模块共享的功能(如日志、事务、权限校验、性能监控)抽取出来,形成独立的 “切面”**,在不修改原有业务代码的前提下,通过 “织入” 的方式动态添加到目标方法的执行流程中。
|
||||
  Spring AOP 是 Spring 框架对 AOP 思想的实现,基于**动态代理(JDK 动态代理 + CGLIB 动态代理)**,专注于解决企业级开发中的横切关注点问题。
|
||||
| 术语 | 说明 |
|
||||
|------|------|
|
||||
| 切面(Aspect) | 封装横切逻辑的类(如日志切面、事务切面),包含通知和切点 |
|
||||
| 连接点(Joinpoint) | 程序执行过程中的某个特定点(如方法调用、异常抛出),Spring AOP 中仅支持方法级连接点 |
|
||||
| 切点(Pointcut) | 匹配连接点的规则,用于指定切面要织入到哪些方法上 |
|
||||
| 通知(Advice) | 切面的具体逻辑(如日志打印、权限校验),按执行时机分为 5 种 |
|
||||
| 目标对象(Target) | 被切面织入的目标类(业务类) |
|
||||
| 代理对象(Proxy) | Spring AOP 为目标对象创建的代理类,横切逻辑通过代理执行 |
|
||||
| 织入(Weaving) | 将切面逻辑添加到目标对象的过程,Spring AOP 是运行时织入(通过动态代理) |
|
||||
|
||||
| 通知类型 | 执行时机 |
|
||||
|----------|----------|
|
||||
| 前置通知(Before) | 目标方法执行前执行 |
|
||||
| 后置通知(After) | 目标方法执行后执行(无论是否抛出异常) |
|
||||
| 返回通知(AfterReturning) | 目标方法正常返回后执行 |
|
||||
| 异常通知(AfterThrowing) | 目标方法抛出异常后执行 |
|
||||
| 环绕通知(Around) | 包裹目标方法执行,可自定义目标方法执行前后的逻辑(功能最强) |
|
||||
|
||||
# 二、原理
|
||||
  Spring AOP 基于动态代理实现,分为两种方式:
|
||||
1. JDK 动态代理
|
||||
通过 java.lang.reflect.Proxy 生成代理类,代理类实现目标接口,调用 InvocationHandler 处理横切逻辑;基于接口,无侵入。
|
||||
|
||||
2. CGLIB 动态代理
|
||||
通过继承目标类生成子类作为代理类,重写目标方法,织入横切逻辑;基于继承,目标类不能是 final,方法不能是 final。
|
||||
|
||||
  Spring AOP 自动选择规则:如果目标类实现了接口,使用 JDK 动态代理;否则使用 CGLIB。
|
||||
|
||||
# 三、实战
|
||||
  例如存在以下业务模块:
|
||||
```java
|
||||
@Service
|
||||
public class OrderService {
|
||||
|
||||
// 模拟创建订单
|
||||
public String createOrder(String orderNo) {
|
||||
System.out.println("创建订单:" + orderNo);
|
||||
return "订单创建成功:" + orderNo;
|
||||
}
|
||||
|
||||
// 模拟删除订单(故意抛出异常)
|
||||
public void deleteOrder(String orderNo) {
|
||||
System.out.println("删除订单:" + orderNo);
|
||||
throw new RuntimeException("删除订单失败:订单不存在");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  AOP实现:
|
||||
```java
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// 1. 标记为切面类
|
||||
@Aspect
|
||||
// 2. 交给Spring容器管理
|
||||
@Component
|
||||
public class LogAspect {
|
||||
|
||||
// 3. 定义切点:匹配OrderService类的所有方法
|
||||
@Pointcut("execution(* com.example.aopdemo.service.OrderService.*(..))")
|
||||
public void orderServicePointcut() {}
|
||||
|
||||
// 4. 前置通知:目标方法执行前执行
|
||||
@Before("orderServicePointcut()")
|
||||
public void beforeAdvice(JoinPoint joinPoint) {
|
||||
// 获取方法名和参数
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println("[前置通知] 方法:" + methodName + ",参数:" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
// 5. 后置通知:目标方法执行后执行(无论是否异常)
|
||||
@After("orderServicePointcut()")
|
||||
public void afterAdvice(JoinPoint joinPoint) {
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
System.out.println("[后置通知] 方法:" + methodName + " 执行完毕");
|
||||
}
|
||||
|
||||
// 6. 返回通知:目标方法正常返回后执行
|
||||
@AfterReturning(value = "orderServicePointcut()", returning = "result")
|
||||
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
System.out.println("[返回通知] 方法:" + methodName + ",返回值:" + result);
|
||||
}
|
||||
|
||||
// 7. 异常通知:目标方法抛出异常后执行
|
||||
@AfterThrowing(value = "orderServicePointcut()", throwing = "ex")
|
||||
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
System.out.println("[异常通知] 方法:" + methodName + ",异常:" + ex.getMessage());
|
||||
}
|
||||
|
||||
// 8. 环绕通知:包裹目标方法执行(功能最强)
|
||||
@Around("orderServicePointcut()")
|
||||
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
|
||||
String methodName = proceedingJoinPoint.getSignature().getName();
|
||||
Object[] args = proceedingJoinPoint.getArgs();
|
||||
|
||||
// 前置逻辑
|
||||
System.out.println("[环绕通知-前置] 方法:" + methodName + ",参数:" + Arrays.toString(args));
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
// 执行目标方法
|
||||
result = proceedingJoinPoint.proceed();
|
||||
// 返回后逻辑
|
||||
System.out.println("[环绕通知-返回] 方法:" + methodName + ",返回值:" + result);
|
||||
} catch (Throwable e) {
|
||||
// 异常逻辑
|
||||
System.out.println("[环绕通知-异常] 方法:" + methodName + ",异常:" + e.getMessage());
|
||||
throw e; // 必须抛出异常,否则上层无法捕获
|
||||
} finally {
|
||||
// 后置逻辑
|
||||
System.out.println("[环绕通知-后置] 方法:" + methodName + " 执行完毕");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  测试:
|
||||
```java
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
public class OrderServiceTest {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
// 测试正常方法(createOrder)
|
||||
@Test
|
||||
public void testCreateOrder() {
|
||||
orderService.createOrder("ORDER_20251225");
|
||||
}
|
||||
|
||||
// 测试异常方法(deleteOrder)
|
||||
@Test
|
||||
public void testDeleteOrder() {
|
||||
try {
|
||||
orderService.deleteOrder("ORDER_9999");
|
||||
} catch (Exception e) {
|
||||
// 捕获异常,避免测试中断
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  测试结果:
|
||||
```plaintext
|
||||
[环绕通知-前置] 方法:createOrder,参数:[ORDER_20251225]
|
||||
[前置通知] 方法:createOrder,参数:[ORDER_20251225]
|
||||
创建订单:ORDER_20251225
|
||||
[环绕通知-返回] 方法:createOrder,返回值:订单创建成功:ORDER_20251225
|
||||
[返回通知] 方法:createOrder,返回值:订单创建成功:ORDER_20251225
|
||||
[后置通知] 方法:createOrder 执行完毕
|
||||
[环绕通知-后置] 方法:createOrder 执行完毕
|
||||
|
||||
|
||||
[环绕通知-前置] 方法:deleteOrder,参数:[ORDER_9999]
|
||||
[前置通知] 方法:deleteOrder,参数:[ORDER_9999]
|
||||
删除订单:ORDER_9999
|
||||
[环绕通知-异常] 方法:deleteOrder,异常:删除订单失败:订单不存在
|
||||
[异常通知] 方法:deleteOrder,异常:删除订单失败:订单不存在
|
||||
[后置通知] 方法:deleteOrder 执行完毕
|
||||
[环绕通知-后置] 方法:deleteOrder 执行完毕
|
||||
```
|
||||
|
||||
# 四、切点表达式
|
||||
| 切点表达式 | 说明 |
|
||||
|------------|------|
|
||||
| `execution(* com.example.service.*.*(..))` | 匹配 com.example.service 包下所有类的所有方法 |
|
||||
| `execution(public String com.example.service.OrderService.create*(String))` | 匹配 OrderService 类中公共、返回值为 String、方法名以 create 开头、参数为 String 的方法 |
|
||||
| `execution(* com.example..*Service.*(..))` | 匹配 com.example 包及其子包下所有以 Service 结尾的类的所有方法 |
|
||||
| `@annotation(com.example.annotation.Log)` | 匹配标注了 @Log 自定义注解的方法 |
|
||||
|
||||
  组合切点:
|
||||
```java
|
||||
// 匹配OrderService的方法 且 方法名包含"Order"
|
||||
@Pointcut("execution(* com.example.service.OrderService.*(..)) && execution(* *Order*(..))")
|
||||
public void combinePointcut() {}
|
||||
```
|
||||
|
||||
# 五、总结
|
||||
  通俗的解释,AOP本质是:找目标(通过切点规则,精准找到要 “加戏” 的方法 / 类)→ 造代理(Spring 自动给这些目标生成一个 “中间商”(代理对象))→ 加戏(在目标方法真正执行前 / 后 / 出问题时,让这个 “中间商” 帮你干额外的活)。
|
||||
|
||||
::: tip
|
||||
基于 Spring AOP 的知名框架/组件汇总
|
||||
| 框架/组件 | 核心作用 | Spring AOP 应用说明 |
|
||||
|-----------|----------|---------------------|
|
||||
| AspectJ | 提供切点表达式语法、注解解析(@Aspect/@Pointcut 等) | Spring AOP 仅复用其语法规则,底层仍为自身动态代理(未使用 AspectJ 编译期织入) |
|
||||
| Spring Core | 容器管理、动态代理创建(JDK/CGLIB) | 负责切面/目标类的生命周期管理,生成代理对象并完成织入逻辑 |
|
||||
| Spring Transaction | 声明式事务管理 | 切点:匹配 @Transactional 注解方法;<br>通知:方法执行前开启事务、执行后提交、异常时回滚 |
|
||||
| Spring Cache | 声明式缓存管理 | 切点:匹配 @Cacheable/@CacheEvict 等注解方法;<br>通知:方法执行前查缓存、执行后更新缓存 |
|
||||
| spring-boot-starter-aop | AOP 基础依赖封装 | 自动引入 AspectJ + Spring AOP 核心,开箱即用 |
|
||||
| Spring Security | 安全权限管理 | 切点:匹配 @PreAuthorize 等权限注解方法;<br>通知:方法执行前校验用户权限,无权限抛异常 |
|
||||
| MyBatis-Plus | 增强 MyBatis CRUD 操作 | 切点:匹配 Mapper 接口方法/标注注解的实体类;<br>通知:织入分页逻辑、注解(@TableName)解析等 |
|
||||
| Shiro | 权限管理框架 | 切点:匹配 @RequiresPermissions 注解方法;<br>通知:方法执行前校验用户权限 |
|
||||
| Seata | 分布式事务框架 | 切点:匹配 @GlobalTransactional 注解方法;<br>通知:织入全局事务的开启、提交、回滚逻辑 |
|
||||
| Sentinel | 流量控制/熔断降级框架 | 切点:匹配 @SentinelResource 注解方法;<br>通知:方法执行前校验流量阈值、熔断规则 |
|
||||
| Dynamic-TP | 动态线程池框架 | 切点:匹配线程池执行方法;<br>通知:织入线程池耗时、队列长度等监控逻辑 |
|
||||
| p6spy | 数据库 SQL 监控 | 切点:拦截 JDBC 操作方法;<br>通知:织入 SQL 打印、执行耗时统计逻辑 |
|
||||
| dynamic-datasource-spring-boot-starter | 动态数据源切换 | 切点:匹配 @DS 注解方法;<br>通知:方法执行前切换数据源、执行后恢复默认数据源 |
|
||||
| xxl-job | 分布式任务调度 | 切点:匹配任务执行方法;<br>通知:织入任务执行日志、监控指标采集逻辑 |
|
||||
| Spring Cloud Sleuth | 微服务链路追踪 | 切点:拦截 HTTP/RPC 调用方法;<br>通知:织入 traceId/spanId 传递、链路信息采集逻辑 |
|
||||
:::
|
||||
72
docs/Web/SpringBoot/SpringBoot-Annotation.md
Normal file
72
docs/Web/SpringBoot/SpringBoot-Annotation.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: SpingBoot注解
|
||||
date: 2025-12-30
|
||||
---
|
||||
|
||||
# 一、注解
|
||||
## 1.1 定义
|
||||
  注解(Annotation)是 JDK5.0 引入的特性,可以理解为:给代码添加的 “元数据”(描述数据的数据),就像给代码贴标签,**本身不直接影响代码执行,但可以被编译器、框架(如 SpringBoot)读取并做相应处理**。
|
||||
|
||||
## 1.2 分类
|
||||
| 类型 | 核心作用 | 典型示例 |
|
||||
|----------------|--------------------|------------------|
|
||||
| 源码注解 | 仅在源码编译阶段生效,编译后注解消失 | `@Override` |
|
||||
| 编译时注解 | 编译期生效,注解信息保留到class文件,但JVM运行时不加载 | Lombok的`@Data` |
|
||||
| 运行时注解 | 整个生命周期都存在(源码→编译→运行),可通过反射动态获取注解信息 | SpringBoot的`@RestController`、`@Service`、`@Transactional` |
|
||||
|
||||
## 1.3 示例
|
||||
```java
|
||||
import java.lang.annotation.*;
|
||||
|
||||
// 1. 注解的元注解(描述注解的注解)
|
||||
@Target(ElementType.METHOD) // 注解作用在方法上
|
||||
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可通过反射获取
|
||||
@Documented // 生成Javadoc时包含该注解
|
||||
public @interface MyAnnotation {
|
||||
// 注解的属性(类似方法,可设置默认值)
|
||||
String value() default "默认描述";
|
||||
int num() default 0;
|
||||
}
|
||||
|
||||
// 2. 使用自定义注解
|
||||
public class AnnotationTest {
|
||||
@MyAnnotation(value = "测试方法", num = 10)
|
||||
public void test() {
|
||||
System.out.println("执行测试方法");
|
||||
}
|
||||
|
||||
// 3. 通过反射读取注解
|
||||
public static void main(String[] args) throws NoSuchMethodException {
|
||||
// 获取方法对象
|
||||
java.lang.reflect.Method method = AnnotationTest.class.getMethod("test");
|
||||
// 判断方法是否有该注解
|
||||
if (method.isAnnotationPresent(MyAnnotation.class)) {
|
||||
// 获取注解实例
|
||||
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
|
||||
// 读取注解属性
|
||||
System.out.println("注解value:" + annotation.value()); // 输出:测试方法
|
||||
System.out.println("注解num:" + annotation.num()); // 输出:10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  @Target:指定注解能作用的位置(如ElementType.METHOD= 方法、ElementType.TYPE= 类 / 接口、ElementType.FIELD= 字段)。
|
||||
  @Retention:指定注解的保留阶段(RUNTIME是 SpringBoot 注解最常用的)
|
||||
  首先定义注解的@Target和@Retention信息,然后设置注解的属性,类似于方法的参数,比如这里的value和num,需要在使用的时候通过命名参数的形式传递过来。如果是RUNTIME类型的注解,可以通过反射来获取方法和注解的参数,实现自定义逻辑功能。
|
||||
|
||||
# 二、SpringBoot核心注解
|
||||
## 2.1 启动类注解
|
||||
  @SpringBootApplication为SpringBoot启动类注解,由以下三个注解组成:
|
||||
1. @Configuration:标记类为配置类(替代 XML 配置)
|
||||
2. @EnableAutoConfiguration:开启自动配置(SpringBoot 核心,自动配置 Tomcat、数据库连接等)
|
||||
3. @ComponentScan:扫描当前包及子包下的 @Component、@Service 等注解的类,纳入 Spring 容器管理
|
||||
|
||||
## 2.2 组件注册注解
|
||||
| 注解 | 作用 | 使用场景 |
|
||||
|----------------|--------------------------|------------------------------------------------------------------|
|
||||
| @Component | 通用组件注解 | 通用工具类、非业务层类 |
|
||||
| @Controller | 控制器注解 | MVC 的控制层(返回页面) |
|
||||
| @RestController| REST 控制器 | API 接口层(返回 JSON/XML,= @Controller + @ResponseBody) |
|
||||
| @Service | 服务层注解 | 业务逻辑层 |
|
||||
| @Repository | 数据访问层注解 | DAO 层 / 持久层(如 MyBatis 的 Mapper 接口) |
|
||||
877
docs/Web/SpringBoot/SpringBoot-Common.md
Normal file
877
docs/Web/SpringBoot/SpringBoot-Common.md
Normal file
@@ -0,0 +1,877 @@
|
||||
---
|
||||
title: SpringBoot Common
|
||||
date: 2025-12-15
|
||||
---
|
||||
|
||||
# 一、框架说明
|
||||
  本框架基于Spring Boot3框架二次开发,增加了依赖包管理和启动项配置等功能。
|
||||
|
||||
# 二、项目结构
|
||||
|
||||
| 项目模块 | 模块含义 | 主要功能 |
|
||||
| :-----------: | :-------------------: | :---------------------------------: |
|
||||
| starters | SpringBoot 启动项 | 主要包括web、jdbc和log启动项配置 |
|
||||
| autoconfigure | staters具体启动配置类 | 主要包括web和jdbc具体的启动项配置类 |
|
||||
| dependencies | 依赖项 | 主要包括本框架中的依赖包管理 |
|
||||
| framework | 通用配置 | 主要包括web和data的一些通用工具方法 |
|
||||
|
||||
# 三、项目说明
|
||||
## 3.1 common模块
|
||||
  该模块主要声明项目结构,包括autoconfigure、dependencies、framework、starter-parent和starters等模块。
|
||||
```xml
|
||||
<modules>
|
||||
<!-- modules表示聚合关系,即common有以下模块 -->
|
||||
<module>dependencies</module>
|
||||
<module>starters</module>
|
||||
<module>framework</module>
|
||||
<module>autoconfigure</module>
|
||||
<module>starter-parent</module>
|
||||
</modules>
|
||||
```
|
||||
|
||||
## 3.2 dependencies模块
|
||||
  该模块为其他模块的父模块,声明了一些常用依赖包及版本,通过`<dependencyManagement>`管理,只声明依赖的版本,并不会实际引入依赖。
|
||||
  后续有新增依赖时,需要先在dependencies模块声明版本,然后在相应的starter模块中增加依赖。
|
||||
```xml
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
|
||||
<spring.boot.version>3.5.0</spring.boot.version>
|
||||
<springdoc.version>2.7.0</springdoc.version>
|
||||
|
||||
<logback-more-appenders.version>1.8.8</logback-more-appenders.version>
|
||||
<fluency-fluentd.version>2.7.0</fluency-fluentd.version>
|
||||
|
||||
<mybatis-spring.version>3.0.4</mybatis-spring.version>
|
||||
<mybatis.plus.version>3.5.12</mybatis.plus.version>
|
||||
<sa.token.version>1.43.0</sa.token.version>
|
||||
<hutool.version>5.8.26</hutool.version>
|
||||
|
||||
<commons.io.version>2.19.0</commons.io.version>
|
||||
<commons.collections.version>4.4</commons.collections.version>
|
||||
<commons-lang3.version>3.14.0</commons-lang3.version>
|
||||
<commons.net.version>3.9.0</commons.net.version>
|
||||
<guava.version>31.1-jre</guava.version>
|
||||
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||
<yitter.idgenerator.version>1.0.6</yitter.idgenerator.version>
|
||||
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
<lombok.mapstruct.version>0.2.0</lombok.mapstruct.version>
|
||||
|
||||
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
|
||||
<docker-maven-plugin.version>0.41.0</docker-maven-plugin.version>
|
||||
<native-maven-plugin.version>0.10.2</native-maven-plugin.version>
|
||||
</properties>
|
||||
|
||||
<!-- dependencyManagement部分只声明依赖的版本,并不会实际引入依赖。
|
||||
需要在具体的模块中显式声明依赖,才能让模块使用这些库 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringDoc OpenAPI + Swagger UI -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展 logback appender -->
|
||||
<dependency>
|
||||
<groupId>com.sndyuk</groupId>
|
||||
<artifactId>logback-more-appenders</artifactId>
|
||||
<version>${logback-more-appenders.version}</version>
|
||||
</dependency>
|
||||
<!-- Fluentd 日志搜集和转发 -->
|
||||
<dependency>
|
||||
<groupId>org.komamitsu</groupId>
|
||||
<artifactId>fluency-fluentd</artifactId>
|
||||
<version>${fluency-fluentd.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SaToken BOM -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-bom</artifactId>
|
||||
<version>${sa.token.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- hutool BOM -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>${mybatis-spring.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis-Plus Maven BOM -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-bom</artifactId>
|
||||
<version>${mybatis.plus.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- IO工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons.io.version}</version>
|
||||
</dependency>
|
||||
<!-- 集合工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${commons.collections.version}</version>
|
||||
</dependency>
|
||||
<!-- 字符串工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<!-- 网络工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>${commons.net.version}</version>
|
||||
</dependency>
|
||||
<!-- 工具类(集合、缓存、并发、IO、字符串) -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava-bom</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 实体映射工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 雪花ID生成器 -->
|
||||
<dependency>
|
||||
<groupId>com.github.yitter</groupId>
|
||||
<artifactId>yitter-idgenerator</artifactId>
|
||||
<version>${yitter.idgenerator.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- starter中的依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-logging</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-web</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-jdbc</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<version>${docker-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.graalvm.buildtools</groupId>
|
||||
<artifactId>native-maven-plugin</artifactId>
|
||||
<version>${native-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
```
|
||||
|
||||
::: tip
|
||||
dependencyManagement部分只声明依赖的版本,并不会实际引入依赖。
|
||||
需要在具体的模块中显式声明依赖,才能让模块使用这些库
|
||||
:::
|
||||
|
||||
## 3.3 framework模块
|
||||
  该模块主要是一些通用的配置。
|
||||
|
||||
### 3.3.1 data
|
||||
  主要提供了2个数据库实体类的基类。
|
||||
::: code-group
|
||||
```java [AbstractIdEntity]
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class AbstractIdEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
}
|
||||
```
|
||||
|
||||
```java [AbstractEntity]
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class AbstractEntity extends AbstractIdEntity {
|
||||
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(value = "create_by", fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 3.3.2 web
|
||||
  主要提供了自定义异常处理类。
|
||||
::: code-group
|
||||
```java [ErrorResponse]
|
||||
/**
|
||||
* 错误响应
|
||||
*/
|
||||
public final class ErrorResponse {
|
||||
/**
|
||||
* 自定义code
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 自定义消息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
public ErrorResponse(String message) {
|
||||
this.code = "Unspecified";
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ErrorResponse(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [CustomException]
|
||||
/**
|
||||
* 自定义异常类
|
||||
*/
|
||||
public class CustomException extends AbstractException {
|
||||
|
||||
public CustomException(String errorMessage) {
|
||||
super(errorMessage);
|
||||
}
|
||||
|
||||
public CustomException(String code, String errorMessage) {
|
||||
super(code, errorMessage);
|
||||
}
|
||||
|
||||
public CustomException(String errorMessage, Exception innerException) {
|
||||
super(errorMessage, innerException);
|
||||
}
|
||||
|
||||
public CustomException(String code, String errorMessage, Exception innerException) {
|
||||
super(code, errorMessage, innerException);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java [AbstractException]
|
||||
/**
|
||||
* 抽象异常类
|
||||
*/
|
||||
public abstract class AbstractException extends RuntimeException {
|
||||
protected String code;
|
||||
|
||||
public AbstractException(String errorMessage) {
|
||||
super(errorMessage);
|
||||
}
|
||||
|
||||
public AbstractException(String code, String errorMessage) {
|
||||
super(errorMessage);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public AbstractException(String errorMessage, Exception innerException) {
|
||||
super(errorMessage, innerException);
|
||||
}
|
||||
|
||||
public AbstractException(String code, String errorMessage, Exception innerException) {
|
||||
super(errorMessage, innerException);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 3.4 starters模块
|
||||
  该模块为整个项目的核心模块,在实际项目中,通过引入相应的starter模块,并配合autoconfigure模块中的功能,即可实现快速自动装配。
|
||||
  该模块的pom.xml声明的依赖会注入到实际的项目中。
|
||||
::: danger
|
||||
在SringBoot3中,自动配置的路径改成了**main->resources->META-INF->spring->org.springframework.boot.autoconfigure.AutoConfiguration.imports**路径,然后在该文件中声明需要自动执行的类。
|
||||
之前的SringBoot2,是在**main->resources->META-INF**目录下新建spring.factories文件。
|
||||
:::
|
||||
|
||||
#### 3.4.1 stater-jdbc
|
||||
  该模块主要提供数据库相关的配置功能。
|
||||
  目前实现的功能有:
|
||||
1. 配置MybatisPlus拦截器,添加乐观锁和分页插件。
|
||||
2. 自定义MybatisPlus ID生成器(雪花ID)。
|
||||
3. 配置MybatisPlus自动填充字段(create_time、create_by、update_time和update_by)。
|
||||
|
||||
::: code-group
|
||||
```java [JdbcAutoConfiguration]
|
||||
/**
|
||||
* jdbc自动配置
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(SqlSessionFactory.class)
|
||||
@Import({YitterGenerator.class, MybatisMetaObjectHandler.class})
|
||||
public class JdbcAutoConfiguration {
|
||||
/**
|
||||
* MybatisPlus拦截器
|
||||
* @return 拦截器
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [YitterGenerator]
|
||||
/**
|
||||
* 雪花id生成器
|
||||
*/
|
||||
public class YitterGenerator implements IdentifierGenerator {
|
||||
|
||||
@Override
|
||||
public Number nextId(Object entity) {
|
||||
return YitIdHelper.nextId();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java [MybatisMetaObjectHandler]
|
||||
/**
|
||||
* Mybatis Plus自动填充
|
||||
*/
|
||||
public class MybatisMetaObjectHandler implements MetaObjectHandler {
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, Constants.CREATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, Constants.UPDATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||||
|
||||
if (StpUtil.isLogin()) {
|
||||
this.strictInsertFill(metaObject, Constants.CREATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||||
this.strictInsertFill(metaObject, Constants.UPDATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, Constants.UPDATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||||
|
||||
if (StpUtil.isLogin()) {
|
||||
this.strictUpdateFill(metaObject, Constants.UPDATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
  `@ConditionalOnClass(SqlSessionFactory.class)`表示只有存在SqlSessionFactory类时,这个配置类才会生效。即如果没有 MyBatis 相关依赖,这个配置类会被 Spring 完全忽略。
|
||||
  `@Import`用于导入其他配置类或组件到当前配置类中。
|
||||
|
||||
#### 3.4.2 starter-web
|
||||
  该模块主要提供web相关的配置功能。
|
||||
  目前实现的功能有:
|
||||
1. 注册审计拦截器
|
||||
2. 注册Sa-Token拦截器
|
||||
3. 注册安全拦截器
|
||||
4. 配置CORS跨越
|
||||
5. 全局异常处理器
|
||||
|
||||
::: code-group
|
||||
```java [ServerAutoConfiguration]
|
||||
@Configuration()
|
||||
@Import({DefaultExceptionAdvice.class, AuditBodyAdvice.class, CustomProperties.class})
|
||||
public class ServerAutoConfiguration implements WebMvcConfigurer {
|
||||
@Resource
|
||||
private CustomProperties customProperties;
|
||||
|
||||
/**
|
||||
* 注册拦截器 需要实现 WebMvcConfigurer 接口
|
||||
* @param registry 注册器
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册安全拦截器
|
||||
// registry.addInterceptor(new SecurityInterceptor());
|
||||
// 注册审计拦截器
|
||||
registry.addInterceptor(new AuditInterceptor(customProperties.getBasePackage()));
|
||||
// 注册Sa-Token拦截器 登录校验
|
||||
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
|
||||
.excludePathPatterns("/error", "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**")
|
||||
.excludePathPatterns("/files/**")
|
||||
.excludePathPatterns("/doc.html", "/webjars/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置CORS跨越 需要实现 WebMvcConfigurer 接口
|
||||
* @return 过滤器
|
||||
*/
|
||||
@Bean
|
||||
@Order(-128)
|
||||
public CorsFilter corsFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
// 允许所有来源
|
||||
config.addAllowedOrigin("*");
|
||||
// 允许所有请求头
|
||||
config.addAllowedHeader("*");
|
||||
// 允许所有请求方法
|
||||
config.addAllowedMethod("*");
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [AuditInterceptor]
|
||||
public class AuditInterceptor implements HandlerInterceptor {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuditInterceptor.class);
|
||||
|
||||
private final String basePackage;
|
||||
|
||||
public AuditInterceptor(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 如果是Http请求
|
||||
if (handler instanceof HandlerMethod hd) {
|
||||
request.setAttribute(Constants.AUDIT_TIME_FLAG, System.currentTimeMillis());
|
||||
// 判断该请求是否需要审计
|
||||
if (WebUtils.checkIsAuditPackages(hd.getBeanType().getPackage(), basePackage)) {
|
||||
handleAuditPackageRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
// true表示继续处理请求
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
// 这里我们不需要处理时间计算,所有的计算在 afterCompletion 中完成
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
// 获取开始时间
|
||||
Long startTime = (Long) request.getAttribute(Constants.AUDIT_TIME_FLAG);
|
||||
if (startTime != null) {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
logger.info("<AuditSummary> Request URL {} | Time Taken {} ms", request.getRequestURI(), duration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理审计包请求
|
||||
* @param request 请求
|
||||
*/
|
||||
private void handleAuditPackageRequest(HttpServletRequest request) {
|
||||
if (StringUtils.isEmpty(request.getQueryString())) {
|
||||
logger.info("<AuditSummary> {} {}", request.getMethod(), request.getRequestURI());
|
||||
} else {
|
||||
logger.info("<AuditSummary> {} {}?{}", request.getMethod(), request.getRequestURI(), WebUtils.format2UTF8(request.getQueryString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [DefaultExceptionAdvice]
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class DefaultExceptionAdvice {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionAdvice.class);
|
||||
|
||||
/**
|
||||
* 处理SaToken权限错误
|
||||
* @param e 权限异常
|
||||
* @return 错误响应
|
||||
*/
|
||||
@ExceptionHandler(SaTokenException.class)
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public ErrorResponse handleSaTokenException(SaTokenException e) {
|
||||
ErrorResponse response = new ErrorResponse(String.valueOf(e.getCode()), e.getMessage());
|
||||
logger.error("<{}> {}", response.getCode(), e.toString());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验错误
|
||||
* @param e 校验异常
|
||||
* @return 错误响应
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ErrorResponse handleValidationException(MethodArgumentNotValidException e) {
|
||||
ErrorResponse response = new ErrorResponse(WebUtils.formatValidationException(e.getBindingResult().getFieldErrors()));
|
||||
logger.error("<{}> {}", response.getCode(), response.getMessage());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义异常错误
|
||||
* @param e 自定义异常
|
||||
* @return 错误响应
|
||||
*/
|
||||
@ExceptionHandler(AbstractException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ErrorResponse handleAbstractException(AbstractException e) {
|
||||
ErrorResponse response;
|
||||
if (e.getCode() == null || e.getCode().isEmpty()) {
|
||||
response = new ErrorResponse(e.getMessage());
|
||||
} else {
|
||||
response = new ErrorResponse(e.getCode(), e.getMessage());
|
||||
}
|
||||
logger.error(String.format("<%s> ", response.getCode()), e);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理默认异常错误
|
||||
* @param e 默认异常
|
||||
* @return 错误响应
|
||||
*/
|
||||
@ExceptionHandler
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ErrorResponse handleDefaultException(Exception e) {
|
||||
ErrorResponse response = new ErrorResponse(e.getMessage());
|
||||
logger.error(String.format("<%s> ", response.getCode()), e);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [AuditBodyAdvice]
|
||||
@ControllerAdvice
|
||||
@Import({CustomProperties.class})
|
||||
public class AuditBodyAdvice implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
|
||||
@Resource
|
||||
private CustomProperties customProperties;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuditBodyAdvice.class);
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// 判断是否为需要审计的包
|
||||
String auditPackages = customProperties.getBasePackage();
|
||||
return WebUtils.checkIsAuditPackages(methodParameter.getDeclaringClass().getPackage(), auditPackages)
|
||||
&& AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
|
||||
return inputMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// 入参结束
|
||||
try {
|
||||
String jsonBody = objectMapper.writeValueAsString(body);
|
||||
logger.info("<AuditRequest> {}", jsonBody);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.info("<AuditResponse> {}", body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// 判断是否为需要审计的包
|
||||
String auditPackages = customProperties.getBasePackage();
|
||||
return WebUtils.checkIsAuditPackages(returnType.getDeclaringClass().getPackage(), auditPackages)
|
||||
&& AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
// 出参结束
|
||||
if (body != null && !(body instanceof ErrorResponse)) {
|
||||
try {
|
||||
String jsonBody = objectMapper.writeValueAsString(body);
|
||||
logger.info("<AuditResponse> {}", jsonBody);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.info("<AuditResponse> {}", body);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java [CustomProperties]
|
||||
/**
|
||||
* 用于将配置文件(如 application.properties 或 application.yml)中的属性值绑定到 Java 对象
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "web-starter")
|
||||
public class CustomProperties {
|
||||
// yml中web-starter下的base-package字段值
|
||||
private String basePackage = "";
|
||||
|
||||
public String getBasePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
public void setBasePackage(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
  `@ConfigurationProperties`用于将配置文件(如 application.properties 或 application.yml)中的属性值绑定到 Java 对象
|
||||
|
||||
::: tip
|
||||
SpringBoot MVC总体执行顺序:
|
||||
1. 进入Tomcat容器
|
||||
2. 进入Filter过滤器
|
||||
3. 进入Servlet容器
|
||||
4. 进入Interceptor拦截器
|
||||
5. 进入Controller控制器
|
||||
6. 进入AOP
|
||||
:::
|
||||
|
||||
::: tip
|
||||
请求和响应体拦截器
|
||||
RequestBodyAdvice, ResponseBodyAdvice主要发生在Controller执行前后:
|
||||
1. preHandle():请求处理前
|
||||
2. beforeBodyRead:请求体反序列化前
|
||||
3. @RequestBody:Controller方法参数绑定
|
||||
4. Controller:请求处理
|
||||
5. beforeBodyWrite():响应体序列化之前
|
||||
6. postHandle():请求处理后
|
||||
7. 视图渲染
|
||||
8. afterCompletion():请求结束
|
||||
:::
|
||||
|
||||
::: tip
|
||||
审计拦截器 可以实现请求日志打印等功能
|
||||
HandlerInterceptor 拦截器执行顺序:
|
||||
1. preHandle():请求处理前 按注册顺序依次执行。
|
||||
2. Controller:请求处理 请求到达Controller并被处理。
|
||||
3. postHandle():请求处理后,视图渲染前 按注册顺序逆序执行。
|
||||
4. afterCompletion():视图渲染后 按注册顺序逆序执行。
|
||||
:::
|
||||
|
||||
# 四、项目发布
|
||||
|
||||
## 4.1 发布到Git中
|
||||
1. 在Gitee/Github中创建工程,需要在工程中创建一个文件夹,例如repo,后续发布的文件要放在该文件夹下。
|
||||
2. 在maven的setting.xml中添加server和repository信息
|
||||
```xml
|
||||
<servers>
|
||||
<server>
|
||||
<id>gitee</id>
|
||||
<username>Cxx0822</username>
|
||||
<password>token</password>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>gitee</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>https://maven.aliyun.com/repository/central</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>gitee</id>
|
||||
<url>https://gitee.com/Cxx0822/springboot2-common/raw/master/repo</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>central</id>
|
||||
<url>https://maven.aliyun.com/repository/central</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<activeProfiles>
|
||||
<activeProfile>gitee</activeProfile>
|
||||
</activeProfiles>
|
||||
```
|
||||
|
||||
  注:这里要去掉mirror的阿里云镜像。
|
||||
|
||||
3. 在工程的根目录的pom.xml中添加发布配置:
|
||||
```xml
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>gitee</id>
|
||||
<name>springboot2-common</name>
|
||||
<url>file:D:/temp/maven</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
```
|
||||
|
||||
  注:gitee不支持通过deploy发布jar包,可以先发布到本地,再将文件复制到项目文件夹下,通过git push推送。
|
||||
|
||||
4. 将本地产生的发布文件上传至git仓库中。
|
||||
5. 其他项目引用:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-web</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-jdbc</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-logging</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
# 五、项目使用
|
||||
1. 将父工程改为starter-parent模块
|
||||
```xml
|
||||
<parent>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-parent</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
```
|
||||
2. 根据需要引入starter-web、starter-jdbc和starter-logging模块
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-web</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-jdbc</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-logging</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
3. 打包模块时,需要添加spring-boot-maven-plugin
|
||||
```xml
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
```
|
||||
142
docs/Web/SpringBoot/SpringBoot-Redis.md
Normal file
142
docs/Web/SpringBoot/SpringBoot-Redis.md
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: SpringBoot Redis简介和使用
|
||||
date: 2026-03-22
|
||||
---
|
||||
|
||||
# 一、简介
|
||||
  Redis是一个高性能的开源内存数据库,用作缓存、数据库和消息中间件。它以极快的读写速度(基于内存存储)支持字符串、列表、哈希等多种数据结构。Redis提供数据持久化、主从复制、哨兵模式等高可用特性,广泛应用于缓存、会话存储、排行榜、消息队列等场景。
|
||||
|
||||
# 二、安装
|
||||
1. 通过apt包管理器安装:
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install redis-server
|
||||
sudo systemctl enable redis-server
|
||||
```
|
||||
|
||||
2. 配置远程和密码访问(可选)
|
||||
|
||||
  打开`/etc/redis/redis.conf`文件,更改配置:
|
||||
```conf
|
||||
bind 0.0.0.0 ::1
|
||||
protected-mode yes
|
||||
requirepass password
|
||||
```
|
||||
|
||||
  `systemctl restart redis-server`重启服务。
|
||||
|
||||
# 三、SpringBoot使用
|
||||
## 3.1 引入依赖
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 3.2 配置文件
|
||||
```yml
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
```
|
||||
|
||||
::: tip
|
||||
SpringBoot2中没有data层级
|
||||
:::
|
||||
|
||||
## 3.3 简单使用
|
||||
```java
|
||||
@Component
|
||||
public class RedisUtils {
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate redis;
|
||||
|
||||
public void set(String key, String value, long seconds) {
|
||||
redis.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public String get(String key) {
|
||||
return redis.opsForValue().get(key);
|
||||
}
|
||||
|
||||
public void delete(String key) {
|
||||
redis.delete(key);
|
||||
}
|
||||
|
||||
public Boolean hasKey(String key) {
|
||||
return redis.hasKey(key);
|
||||
}
|
||||
|
||||
// 匹配查询
|
||||
public Set<String> scanKeys(String pattern) {
|
||||
return redis.execute((RedisCallback<Set<String>>) connection -> {
|
||||
Set<String> keys = new HashSet<>();
|
||||
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(1000).build();
|
||||
|
||||
try (Cursor<byte[]> cursor = connection.keyCommands().scan(options)) {
|
||||
while (cursor.hasNext()) {
|
||||
keys.add(new String(cursor.next(), StandardCharsets.UTF_8));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Redis SCAN 执行失败,pattern=" + pattern, e);
|
||||
}
|
||||
|
||||
return keys;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
生产环境中使用`scan`命令批量查询`key`值,`keys`命令会阻塞`Redis`线程。
|
||||
:::
|
||||
|
||||
# 四、实战
|
||||
## 4.1 设备在线功能
|
||||
  实现一个统计设备是否实时在线的功能。
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/device")
|
||||
public class DeviceController {
|
||||
|
||||
@Resource
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
private static final String HEARTBEAT_KEY = "device:heartbeat:";
|
||||
private static final long EXPIRE_SECONDS = 40;
|
||||
|
||||
/**
|
||||
* 设备心跳接口
|
||||
* 设备每20-30秒调用一次
|
||||
* 心跳key 40秒后过期,过期即认为设备离线
|
||||
*/
|
||||
@PostMapping("/heartbeat")
|
||||
public Boolean heartbeat(@RequestParam String deviceId) {
|
||||
redisUtils.set(HEARTBEAT_KEY + deviceId, String.valueOf(System.currentTimeMillis()), EXPIRE_SECONDS);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前在线设备列表
|
||||
*/
|
||||
@GetMapping("/online")
|
||||
public Set<String> getOnlineDevices() {
|
||||
Set<String> keys = redisUtils.scanKeys(HEARTBEAT_KEY + "*");
|
||||
Set<String> deviceIds = new HashSet<>();
|
||||
|
||||
for (String key : keys) {
|
||||
String deviceId = key.substring(HEARTBEAT_KEY.length());
|
||||
deviceIds.add(deviceId);
|
||||
}
|
||||
|
||||
return deviceIds;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  客户端每30秒调用一次`/device/heartbeat`接口。
|
||||
134
docs/Web/SpringBoot/SpringBoot-RestClient.md
Normal file
134
docs/Web/SpringBoot/SpringBoot-RestClient.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
title: SpringBoot RestClient简介
|
||||
date: 2026-01-06
|
||||
---
|
||||
|
||||
# 一、简介
|
||||
  RestClient 是 Spring Framework 6.1(及对应的 Spring Boot 3.2+)推出的新一代同步 HTTP 客户端,设计目标是替代传统的 RestTemplate(Spring 已标记 RestTemplate 为维护模式,不再新增功能),同时结合了 WebClient 的流畅 API 设计,又保持了 RestTemplate 的同步、简单易用的特点。
|
||||
|
||||
# 二、使用
|
||||
## 2.1 创建实例
|
||||
```java
|
||||
@Configuration
|
||||
public class RestClientConfig {
|
||||
final String baseUrl = "https://demo.com";
|
||||
|
||||
@Bean
|
||||
public RestClient restClient() {
|
||||
|
||||
return RestClient.builder()
|
||||
// 基础RURL
|
||||
.baseUrl(baseUrl)
|
||||
// 默认请求头 请求体为JSON格式
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
// 默认请求头 响应体为JSON格式
|
||||
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2.2 HTTP请求
|
||||
```java
|
||||
@Service
|
||||
public class RestClientService {
|
||||
|
||||
@Resource
|
||||
private RestClient restClient;
|
||||
|
||||
// 1. GET 请求:获取单个资源
|
||||
public Post getPostById(Long id) {
|
||||
return restClient.get() // 指定 GET 方法
|
||||
.uri("/posts/{id}", id) // 请求路径(支持路径参数)
|
||||
.retrieve() // 执行请求并获取响应
|
||||
.body(Post.class); // 解析响应体为Post实体
|
||||
}
|
||||
|
||||
// 2. GET 请求:获取列表并解析为 Map
|
||||
public Map<String, Object>[] getAllPosts() {
|
||||
return restClient.get()
|
||||
.uri("/posts")
|
||||
.retrieve()
|
||||
.body(Map[].class); // 解析为 Map 数组(适合未知结构的 JSON)
|
||||
}
|
||||
|
||||
// 3. POST 请求:提交数据并获取响应
|
||||
public Boolean createPost(Post post) {
|
||||
return restClient.post() // 指定 POST 方法
|
||||
.uri("/posts")
|
||||
.body(post) // 设置请求体
|
||||
.retrieve()
|
||||
.body(Boolean.class);
|
||||
}
|
||||
|
||||
// 4. PUT 请求:更新资源
|
||||
public Boolean updatePost(Long id, Post post) {
|
||||
return restClient.put() // 指定 PUT 方法
|
||||
.uri("/posts/{id}", id)
|
||||
.body(post)
|
||||
.retrieve()
|
||||
.body(Boolean.class);
|
||||
}
|
||||
|
||||
// 5. DELETE 请求:删除资源
|
||||
public void deletePost(Long id) {
|
||||
restClient.delete() // 指定 DELETE 方法
|
||||
.uri("/posts/{id}", id)
|
||||
.retrieve(); // DELETE 请求通常无响应体
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
在.retrieve()之前的是请求阶段,在此之后是响应阶段。
|
||||
因此在此之前的.body()是请求体,在此之后的.body()是响应体。
|
||||
:::
|
||||
|
||||
## 2.3 进阶用法
|
||||
### 2.3.1 设置超时时间
|
||||
```java
|
||||
@Bean
|
||||
public RestClient restClient() {
|
||||
// 创建请求工厂,设置超时
|
||||
ClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory() {{
|
||||
setConnectTimeout(3000); // 连接超时 3 秒
|
||||
setReadTimeout(5000); // 读取超时 5 秒
|
||||
}};
|
||||
|
||||
return RestClient.builder()
|
||||
.baseUrl(baseUrl)
|
||||
.requestFactory(factory) // 设置请求工厂
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3.2 添加请求拦截器
|
||||
```java
|
||||
@Bean
|
||||
public RestClient restClient() {
|
||||
return RestClient.builder()
|
||||
.baseUrl(baseUrl)
|
||||
// 添加拦截器
|
||||
.requestInterceptor(request -> {
|
||||
request.getHeaders().add("Authorization", "Bearer your-token-here");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3.3 自定义响应处理
|
||||
```java
|
||||
public String getPostWithErrorHandling(Long id) {
|
||||
return restClient.get()
|
||||
.uri("/posts/{id}", id)
|
||||
.retrieve()
|
||||
// 自定义状态码处理
|
||||
.onStatus(status -> status.is4xxClientError(), (request, response) -> {
|
||||
throw new RuntimeException("客户端错误:" + response.getStatusCode() + ",路径:" + request.getURI());
|
||||
})
|
||||
.onStatus(status -> status.is5xxServerError(), (request, response) -> {
|
||||
throw new RuntimeException("服务端错误:" + response.getStatusCode());
|
||||
})
|
||||
.body(String.class);
|
||||
}
|
||||
```
|
||||
359
docs/Web/SpringBoot/SpringBoot-Skills.md
Normal file
359
docs/Web/SpringBoot/SpringBoot-Skills.md
Normal file
@@ -0,0 +1,359 @@
|
||||
---
|
||||
title: SpingBoot技巧
|
||||
date: 2025-12-12
|
||||
---
|
||||
|
||||
# 一、JsonView
|
||||
  JsonView 是 Jackson 提供的注解,用于控制对象序列化/反序列化时包含哪些字段。可以实现:
|
||||
- 不同接口返回不同字段
|
||||
- 敏感字段过滤
|
||||
- 前后端数据分离
|
||||
|
||||
## 1.1 添加依赖
|
||||
```xml
|
||||
<dependencies>
|
||||
<!-- Spring Boot Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## 1.2 使用
|
||||
1. 定义视图接口
|
||||
```java
|
||||
// JsonView 视图定义
|
||||
public class Views {
|
||||
// 基础视图 - 所有接口都包含的字段
|
||||
public interface Basic {}
|
||||
|
||||
// 返回给前端时使用
|
||||
public interface Response extends Basic {}
|
||||
|
||||
// 接收前端数据时使用
|
||||
public interface Request extends Basic {}
|
||||
|
||||
// 管理视图 - 包含所有字段
|
||||
public interface AdminView extends ReadView {}
|
||||
}
|
||||
```
|
||||
|
||||
2. Dto对象使用JsonView
|
||||
```java
|
||||
@Data
|
||||
public class UserDTO {
|
||||
@JsonView(Views.Response.class)
|
||||
private Long id;
|
||||
|
||||
@JsonView(Views.Request.class)
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 6, message = "密码至少6位")
|
||||
private String password;
|
||||
|
||||
@JsonView(Views.Basic.class)
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@JsonView(Views.Basic.class)
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
@JsonView(Views.Basic.class)
|
||||
private String nickname;
|
||||
|
||||
@JsonView(AdminView.class)
|
||||
private Boolean isActive;
|
||||
}
|
||||
```
|
||||
|
||||
3. Controller中使用
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@PostMapping
|
||||
@JsonView(UserDTO.Response.class)
|
||||
public UserDTO createUser(@RequestBody @JsonView(UserDTO.Request.class) UserDTO userDTO) {
|
||||
return userService.createUser(userDTO);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@JsonView(UserDTO.Response.class)
|
||||
public UserDTO updateUser(@PathVariable Long id,
|
||||
@RequestBody @JsonView(UserDTO.Request.class) UserDTO userDTO) {
|
||||
return userService.updateUser(id, userDTO);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@JsonView(UserDTO.Response.class)
|
||||
public UserDTO getUser(@PathVariable Long id) {
|
||||
return userService.getUserById(id);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/admin")
|
||||
@JsonView(UserDTO.AdminView.class)
|
||||
public UserDTO getAdminUser(@PathVariable Long id) {
|
||||
return userService.getAdminUserById(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# 二、MapStruct
|
||||
  MapStruct 是一个 Java 注解处理器,用于生成类型安全的 Bean 映射代码:
|
||||
- 编译时生成映射代码,无运行时性能损失
|
||||
- 类型安全
|
||||
- 支持复杂映射
|
||||
|
||||
## 2.1 添加依赖
|
||||
```xml
|
||||
<properties>
|
||||
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
```
|
||||
|
||||
## 2.2 使用
|
||||
```java
|
||||
import org.mapstruct.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
|
||||
@Mapping(target = "name", source = "userName") // 字段名不同
|
||||
@Mapping(target = "phoneNumber", source = "phone") // 字段名不同
|
||||
@Mapping(target = "description", source = "remark") // 字段名不同
|
||||
@Mapping(target = "statusDesc", ignore = true) // 需要特殊处理
|
||||
@Mapping(target = "createTime", ignore = true) // 需要格式化
|
||||
UserDTO toDTO(User user);
|
||||
|
||||
@Mapping(target = "userName", source = "name")
|
||||
@Mapping(target = "phone", source = "phoneNumber")
|
||||
@Mapping(target = "remark", source = "description")
|
||||
@Mapping(target = "password", ignore = true) // 密码不映射
|
||||
User toEntity(UserDTO dto);
|
||||
|
||||
// 列表映射
|
||||
List<UserDTO> toDTOList(List<User> users);
|
||||
|
||||
// 带默认值的映射
|
||||
@Mapping(target = "name", source = "userName")
|
||||
@Mapping(target = "phoneNumber", source = "phone")
|
||||
@Mapping(target = "description", source = "remark", defaultValue = "暂无描述")
|
||||
@Mapping(target = "statusDesc", expression = "java(convertStatus(user.getStatus()))")
|
||||
@Mapping(target = "createTime", expression = "java(formatTime(user.getCreateTime()))")
|
||||
UserDTO toDTOWithDefault(User user);
|
||||
|
||||
// 自定义转换方法
|
||||
default String convertStatus(Integer status) {
|
||||
if (status == null) return "未知";
|
||||
switch (status) {
|
||||
case 0: return "禁用";
|
||||
case 1: return "正常";
|
||||
case 2: return "锁定";
|
||||
default: return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
// 时间格式化方法
|
||||
default String formatTime(LocalDateTime time) {
|
||||
if (time == null) return "";
|
||||
return time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  需要执行`mvn compile`后检查`target/generated-sources`文件夹下是否存在生成的转换代码。
|
||||
|
||||
# 三、Bean Validation
|
||||
  Spring Boot的数据校验基于JSR-303/JSR-380规范(Bean Validation),通常使用Hibernate Validator作为实现。
|
||||
|
||||
## 3.1 添加依赖
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 3.2 常用检验注解
|
||||
  Java Bean Validation 常用注解
|
||||
|
||||
| 注解 | 适用类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `@NotNull` | 任意类型 | 值不能为 null |
|
||||
| `@NotBlank` | CharSequence | 字符串不能为 null,且必须包含至少一个非空白字符 |
|
||||
| `@NotEmpty` | CharSequence, Collection, Map, Array | 字符串/集合/数组不能为 null 且不能为空(长度/大小大于 0) |
|
||||
| `@Size(min=, max=)` | 字符串、集合、数组 | 限制长度或大小在 min 和 max 之间 |
|
||||
| `@Min(value)` | 数值类型 | 数值必须大于或等于指定值 |
|
||||
| `@Max(value)` | 数值类型 | 数值必须小于或等于指定值 |
|
||||
| `@Email` | 字符串 | 字符串必须是合法的电子邮件地址格式 |
|
||||
| `@Pattern(regexp=)` | 字符串 | 字符串必须匹配指定的正则表达式 |
|
||||
| `@Future` / `@Past` | 日期时间类型 | 日期必须在当前时间的未来 / 过去 |
|
||||
|
||||
## 3.3 使用方法
|
||||
```java
|
||||
public class UserDTO {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Size(min = 3, max = 20, message = "用户名长度必须在3到20个字符之间")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 8, message = "密码长度至少为8个字符")
|
||||
private String password;
|
||||
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
}
|
||||
```
|
||||
|
||||
  在Controller的方法参数前使用@Valid或@Validated注解来触发校验:
|
||||
```java
|
||||
@RestController
|
||||
public class UserController {
|
||||
@PostMapping("/users")
|
||||
public String createUser(@Valid @RequestBody UserDTO userDTO) {
|
||||
return "用户创建成功";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.4 处理校验错误
|
||||
```java
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
ex.getBindingResult().getAllErrors().forEach((error) -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String errorMessage = error.getDefaultMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
});
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.5 自定义注解
|
||||
```java
|
||||
@Documented
|
||||
@Constraint(validatedBy = PhoneValidator.class)
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface PhoneNumber {
|
||||
String message() default "手机号码格式不正确";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
```
|
||||
|
||||
  校验规则:
|
||||
```java
|
||||
public class PhoneValidator implements ConstraintValidator<PhoneNumber, String> {
|
||||
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return true; // 使用@NotBlank等注解处理空值
|
||||
}
|
||||
return PHONE_PATTERN.matcher(value).matches();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  使用自定义校验:
|
||||
```java
|
||||
public class UserDTO {
|
||||
@PhoneNumber
|
||||
private String mobile;
|
||||
}
|
||||
```
|
||||
|
||||
# 四、ConfigurationProperties注解
|
||||
  @ConfigurationProperties 是 Spring Boot 提供的核心注解,用于将配置文件(如 application.yml/application.properties)中的属性批量绑定到 Java 类的字段上,相比 @Value 注解,它更适合管理一组有层级、有前缀的配置,代码更整洁、可维护性更高。
|
||||
|
||||
## 4.1 配置文件
|
||||
```yml
|
||||
app:
|
||||
name: SpringBootDemo
|
||||
version: 1.0.0
|
||||
author:
|
||||
name: 张三
|
||||
age: 25
|
||||
servers:
|
||||
- 192.168.1.100
|
||||
- 192.168.1.101
|
||||
- 192.168.1.102
|
||||
```
|
||||
|
||||
## 4.2 配置属性类
|
||||
```java
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "app")
|
||||
@Getter
|
||||
@Setter
|
||||
public class AppProperties {
|
||||
// 对应 app.name
|
||||
private String name;
|
||||
// 对应 app.version
|
||||
private String version;
|
||||
// 嵌套属性 - 对应 app.author
|
||||
private Author author;
|
||||
// 集合属性 - 对应 app.servers
|
||||
private List<String> servers;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Author {
|
||||
private String name;
|
||||
|
||||
private Integer age;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  属性类支持嵌套和集合属性。
|
||||
|
||||
## 4.3 使用
|
||||
  在Service或相应地方注入即可:
|
||||
```java
|
||||
@Autowired
|
||||
private AppProperties appProperties;
|
||||
```
|
||||
287
docs/Web/SpringBoot/SpringBoot-Start-Process.md
Normal file
287
docs/Web/SpringBoot/SpringBoot-Start-Process.md
Normal file
@@ -0,0 +1,287 @@
|
||||
---
|
||||
title: SpringBoot启动流程
|
||||
date: 2025-11-26
|
||||
---
|
||||
|
||||
# 一、启动流程
|
||||

|
||||
|
||||
# 二、 阶段详解
|
||||
## 2.1 SpringApplication初始化
|
||||
**🔹 步骤1.1 - main方法入口执行**
|
||||
- 执行位置:标注@SpringBootApplication的类中的main方法
|
||||
- 核心动作:
|
||||
1. 创建SpringApplication实例对象
|
||||
2. 设置应用的基本配置信息
|
||||
3. 准备启动所需的基础环境
|
||||
|
||||
  main方法是整个Spring Boot应用启动的唯一起点。当Java虚拟机开始执行程序时,首先会调用标注有@SpringBootApplication注解的主类中的main方法。在这个方法内部,会创建SpringApplication实例并调用其run方法,正式开启启动流程。
|
||||
  SpringApplication的构造方法会进行一些重要的初始化工作,包括推断主配置类、设置初始引导类等。推断主配置类的过程是通过分析当前线程的堆栈信息来完成的,确保能够准确找到包含main方法的那个类。
|
||||
|
||||
**🔹 步骤1.2 - 加载SpringFactories配置**
|
||||
- 配置源:META-INF/spring.factories文件
|
||||
- 加载内容:
|
||||
1. ApplicationContextInitializer(上下文初始化器)
|
||||
2. ApplicationListener(应用监听器)
|
||||
3. BeanFactoryPostProcessor(Bean工厂后处理器)
|
||||
4. AutoConfigurationImportSelector(自动配置选择器)
|
||||
|
||||
  Spring Boot使用SpringFactoriesLoader机制从类路径下的**META-INF/spring.factories**文件中加载各种扩展组件。**这种机制是Spring Boot自动配置的核心基础,它允许框架和开发者通过标准的配置文件来注册和发现各种扩展实现。**
|
||||
  配置文件中定义了多种类型的组件,包括**应用上下文初始化器、应用事件监听器、Bean工厂后处理器**等。这些组件将在后续的启动过程中按照特定的顺序被执行,共同完成应用的初始化工作。
|
||||
|
||||
**🔹 步骤1.3 - 推断Web应用类型**
|
||||
- 检测逻辑:
|
||||
1. 检查类路径是否存在Servlet相关类
|
||||
2. 检查Spring MVC相关组件
|
||||
3. 检查WebFlux相关组件
|
||||
- 推断结果
|
||||
🅰️ SERVLET:传统Web应用
|
||||
🅱️ REACTIVE:响应式Web应用
|
||||
©️ NONE:非Web应用
|
||||
|
||||
  应用类型推断是Spring Boot自动配置的重要环节。系统会根据项目的依赖情况自动判断应用类型,这直接影响后续创建的应用上下文类型和内嵌服务器的选择。
|
||||
  **推断过程主要通过检查类路径中是否存在特定的类来完成。例如,如果存在Servlet相关的类,就推断为Web应用;如果存在WebFlux相关的类,就推断为响应式Web应用。** 这种基于类路径的自动推断机制使得开发者无需手动配置应用类型,大大简化了配置工作。
|
||||
|
||||
## 2.2 环境准备与配置加载
|
||||
**🔹 步骤2.1 - 创建环境对象**
|
||||
```
|
||||
环境对象层次结构:
|
||||
Environment(环境接口)
|
||||
↓
|
||||
ConfigurableEnvironment(可配置环境)
|
||||
↓
|
||||
具体环境实现(StandardEnvironment/StandardServletEnvironment)
|
||||
├── PropertySources(属性源列表)
|
||||
├── Profiles(激活的配置文件)
|
||||
└── ConversionService(类型转换服务)
|
||||
```
|
||||
|
||||
  环境对象是Spring Boot应用运行时的配置中心,它负责管理所有的配置属性和运行环境信息。根据应用类型的不同,Spring Boot会创建相应的环境实例。
|
||||
  环境对象采用分层设计,提供了统一的属性访问接口,同时支持多种属性源的动态管理。环境对象还负责管理激活的配置文件(Profile),支持基于不同环境的配置隔离和切换。
|
||||
|
||||
**🔹 步骤2.2 - 配置属性源加载**
|
||||
📊 属性源加载优先级(从高到低):
|
||||
| 优先级 | 属性源类型 | 说明 |
|
||||
| - | - | - |
|
||||
| 1 | 命令行参数 | --spring.profiles.active=dev |
|
||||
| 2 | Java系统属性 | System.getProperties() |
|
||||
| 3 | 操作系统环境变量 | 系统级环境配置 |
|
||||
| 4 | 应用配置文件 | application-{profile}.yml/properties |
|
||||
| 5 | 默认属性 | SpringApplication.setDefaultProperties() |
|
||||
|
||||
  属性源加载遵循严格的优先级顺序,确保重要的配置能够覆盖默认配置。这种优先级设计使得配置管理更加灵活,开发者可以通过不同级别的配置来调整应用行为。
|
||||
|
||||
**🔹 步骤2.3 - Profile处理机制**
|
||||
- Profile激活方式:
|
||||
1. 通过spring.profiles.active显式指定
|
||||
2. 通过spring.profiles.include包含其他profile
|
||||
3. 默认使用default profile
|
||||
- Profile解析流程:
|
||||
1. 读取所有可用的profile配置
|
||||
2. 解析条件化配置注解
|
||||
3. 合并不同profile的配置项
|
||||
4. 处理配置覆盖和冲突解决
|
||||
|
||||
  Profile机制是Spring框架提供的环境隔离解决方案,它允许开发者为不同的运行环境定义不同的配置。Spring Boot在启动时会根据激活的Profile来加载相应的配置,并处理配置之间的覆盖关系。
|
||||
  Profile的激活可以通过多种方式实现,包括命令行参数、系统属性、配置文件等。系统支持同时激活多个Profile,并提供了灵活的配置合并策略来处理可能出现的配置冲突。
|
||||
|
||||
**🔹 步骤2.4 - 配置属性绑定**
|
||||
1. 属性值解析:从各种属性源中获取属性值
|
||||
2. 类型转换:将字符串值转换为目标类型
|
||||
3. 数据验证:基于JSR-303进行数据校验
|
||||
4. 对象绑定:将属性值设置到@ConfigurationProperties类
|
||||
|
||||
  配置属性绑定是将外部配置值注入到Java对象中的过程。Spring Boot提供了强大的属性绑定功能,支持宽松的绑定规则、类型转换、数据验证等特性。
|
||||
  绑定过程首先会从环境对象中获取属性值,然后进行类型转换,将字符串类型的配置值转换为目标属性类型。如果配置了验证规则,还会执行数据验证,确保配置值的正确性。最后,将验证通过的属性值设置到目标对象中。
|
||||
|
||||
## 2.3 应用上下文创建与初始化
|
||||
**🔹 步骤3.1 - 创建应用上下文实例**
|
||||
📋 上下文类型映射表:
|
||||
| 应用类型 | 上下文实现类 | 特点描述 |
|
||||
| - | - | - |
|
||||
| Web应用 | AnnotationConfigServletWebServerApplicationContext | 支持Servlet容器 |
|
||||
| 响应式应用 | AnnotationConfigReactiveWebServerApplicationContext | 支持响应式编程 |
|
||||
| 普通应用 | AnnotationConfigApplicationContext | 基础应用上下文 |
|
||||
|
||||
  应用上下文是Spring框架的核心容器,负责管理Bean的生命周期和依赖关系。Spring Boot会根据应用类型创建相应类型的应用上下文实例
|
||||
|
||||
**🔹 步骤3.2 - 应用上下文层次结构**
|
||||
```
|
||||
BeanFactory(Bean工厂基础接口)
|
||||
↓
|
||||
ApplicationContext(应用上下文接口)
|
||||
↓
|
||||
ConfigurableApplicationContext(可配置应用上下文)
|
||||
↓
|
||||
AbstractApplicationContext(抽象实现)
|
||||
↓
|
||||
GenericApplicationContext/AnnotationConfigApplicationContext
|
||||
```
|
||||
|
||||
  应用上下文采用层次化设计,不同层次的接口和类承担不同的职责。这种设计使得上下文的功能可以逐步扩展,同时保持代码的清晰性和可维护性。
|
||||
|
||||
**🔹 步骤3.3 - 执行应用上下文初始化器**
|
||||
- 执行时机:上下文创建后,Bean加载前
|
||||
- 主要功能:
|
||||
1. 🔧 注册自定义Bean定义
|
||||
2. 🔧 设置上下文特定属性
|
||||
3. 🔧 添加特殊的后处理器
|
||||
4. 🔧 配置环境变量覆盖
|
||||
|
||||
  应用上下文初始化器是Spring Boot提供的重要扩展点,允许开发者**在上下文正式刷新之前执行自定义的初始化逻辑。** **初始化器可以通过spring.factories文件注册,也可以通过SpringApplication的addInitializers方法添加。**
|
||||
  **初始化器的主要作用包括注册自定义的Bean定义、配置上下文特定的属性、添加特殊的后处理器等。** 通过初始化器,开发者可以深度定制应用上下文的行为,满足特殊的业务需求。
|
||||
|
||||
**🔹 步骤3.4 - 发布应用上下文事件**
|
||||
事件发布序列:
|
||||
1. ApplicationStartingEvent- 应用启动事件
|
||||
2. ApplicationEnvironmentPreparedEvent- 环境准备完成事件
|
||||
3. ApplicationContextInitializedEvent- 上下文初始化事件
|
||||
4. ApplicationPreparedEvent- 应用准备事件
|
||||
|
||||
  Spring Boot的启动过程采用事件驱动模型,**每个关键步骤都会发布相应的事件**。这种设计使得各个模块之间解耦,同时也便于开发者通过监听事件来扩展启动逻辑。
|
||||
|
||||
## 2.4 Bean定义加载与处理
|
||||
**🔹 步骤4.1 - Bean定义加载方式**
|
||||
多种Bean定义加载途径:
|
||||
1. 🔍 组件扫描:自动扫描@Component、@Service等注解
|
||||
2. 📝 @Bean方法:处理@Configuration类中的@Bean方法
|
||||
3. 📂 @Import导入:导入其他配置类
|
||||
4. 🔗 ImportSelector:动态选择导入的配置类
|
||||
|
||||
  Bean定义加载是Spring容器初始化的核心环节。Spring Boot支持多种Bean定义加载方式,每种方式都有其适用的场景和特点。
|
||||
|
||||
**🔹 步骤4.2 - 组件扫描详细过程**
|
||||
```
|
||||
组件扫描流程:
|
||||
开始扫描指定包路径
|
||||
↓
|
||||
读取包下的所有class文件
|
||||
↓
|
||||
解析类上的注解信息
|
||||
↓
|
||||
识别Spring组件注解
|
||||
↓
|
||||
注册Bean定义到容器
|
||||
↓
|
||||
完成组件扫描
|
||||
```
|
||||
|
||||
  组件扫描是Spring Boot自动配置的基础机制,它能够自动发现和注册项目中的Spring组件。扫描过程基于注解元数据,通过反射机制分析类的结构信息。
|
||||
|
||||
**🔹 步骤4.3 - Bean工厂后处理**
|
||||
| 后处理器 | 功能描述 | 执行时机 |
|
||||
| - | - | - |
|
||||
| ConfigurationClassPostProcessor | 处理@Configuration类 | Bean定义加载后 |
|
||||
| PropertySourcesPlaceholderConfigurer | 处理属性占位符 | 属性解析阶段 |
|
||||
| CustomScopeConfigurer | 注册自定义作用域 | 作用域配置阶段 |
|
||||
|
||||
  BeanFactoryPostProcessor是Spring框架的重要扩展点,允许在Bean实例化之前修改Bean定义信息。Spring Boot在启动过程中会执行多个内置的BeanFactoryPostProcessor。
|
||||
|
||||
## 2.5 Bean实例化与生命周期
|
||||
**🔹 步骤5.1 - Bean实例化策略**
|
||||
实例化顺序规则:
|
||||
1. 🥇 BeanFactoryPostProcessor- 工厂后处理器最先实例化
|
||||
2. 🥈 BeanPostProcessor- Bean后处理器其次实例化
|
||||
3. 🥉 单例Bean- 按依赖顺序实例化普通Bean
|
||||
4. 🏅 其他作用域Bean- 按需实例化
|
||||
|
||||
  Bean实例化遵循特定的顺序规则,确保依赖关系正确的Bean能够按正确的顺序创建。实例化过程采用懒加载和急切实例化相结合的策略。
|
||||
|
||||
**🔹 步骤5.2 - 依赖注入机制**
|
||||
依赖注入的三种方式:
|
||||
1. 🏗️ 构造器注入- 通过构造函数注入依赖
|
||||
2. 🛠️ Setter注入- 通过setter方法注入依赖
|
||||
3. 🎯 字段注入- 直接在字段上使用@Autowired注入
|
||||
|
||||
  依赖注入是Spring框架的核心特性,它通过自动装配机制将Bean之间的依赖关系解耦。Spring支持多种依赖注入方式,每种方式都有其适用的场景。
|
||||
|
||||
**🔹 步骤5.3 - Bean后处理流程**
|
||||
```
|
||||
Bean后处理序列:
|
||||
Bean实例化
|
||||
↓
|
||||
执行BeanPostProcessor.postProcessBeforeInitialization
|
||||
↓
|
||||
执行@PostConstruct方法
|
||||
↓
|
||||
执行InitializingBean.afterPropertiesSet
|
||||
↓
|
||||
执行自定义init方法
|
||||
↓
|
||||
执行BeanPostProcessor.postProcessAfterInitialization
|
||||
↓
|
||||
Bean完全就绪
|
||||
```
|
||||
|
||||
  BeanPostProcessor是Bean生命周期管理的重要扩展点,它允许在Bean初始化前后执行自定义逻辑。Spring Boot内置了多个BeanPostProcessor,用于处理各种注解和AOP代理。
|
||||
|
||||
**🔹 步骤5.4 - 循环依赖解决机制**
|
||||
三级缓存解决方案:
|
||||
1. 一级缓存:存放完全初始化完成的Bean
|
||||
2. 二级缓存:存放早期暴露的Bean(已实例化但未初始化)
|
||||
3. 三级缓存:存放Bean工厂,用于创建Bean的早期引用
|
||||
|
||||
  循环依赖是Spring容器需要解决的重要问题。Spring通过三级缓存机制来解决单例Bean的循环依赖问题,确保即使存在循环引用也能正确完成依赖注入。
|
||||
|
||||
## 2.6 Web服务器启动与配置
|
||||
**🔹 步骤6.1 - 内嵌服务器选择策略**
|
||||
服务器自动配置逻辑:
|
||||
1. 检查类路径中的服务器依赖
|
||||
2. 按优先级选择:Tomcat > Jetty > Undertow
|
||||
3. 根据应用类型创建对应的Web服务器工厂
|
||||
4. 配置服务器参数(端口、上下文路径等)
|
||||
|
||||
  Spring Boot支持多种内嵌服务器,包括Tomcat、Jetty和Undertow。服务器选择基于类路径中的依赖,采用特定的优先级规则。
|
||||
|
||||
**🔹 步骤6.2 - Servlet容器初始化**
|
||||
1. 🎯 创建ServletContext(Servlet上下文)
|
||||
2. 🎯 注册DispatcherServlet(前端控制器)
|
||||
3. 🎯 配置字符编码过滤器
|
||||
4. 🎯 设置会话管理配置
|
||||
5. 🎯 启用静态资源服务
|
||||
|
||||
  Servlet容器初始化是Web应用启动的关键环节。Spring Boot会自动配置Servlet容器,并注册必要的Servlet、Filter和Listener。
|
||||
|
||||
**🔹 步骤6.3 - MVC组件自动配置**
|
||||
自动配置的MVC组件:
|
||||
1. HandlerMapping- 请求映射处理器
|
||||
2. HandlerAdapter- 处理器适配器
|
||||
3. ViewResolver- 视图解析器
|
||||
4. MessageConverter- 消息转换器
|
||||
5. Interceptor- 拦截器配置
|
||||
|
||||
  Spring Boot为Spring MVC提供了完整的自动配置,包括处理器映射、视图解析、消息转换等组件。这些组件基于约定大于配置的原则,提供了合理的默认值。
|
||||
|
||||
## 2.7 启动完成与后处理
|
||||
**🔹 步骤7.1 - 启动事件发布序列**
|
||||
```
|
||||
ApplicationStartingEvent
|
||||
↓
|
||||
ApplicationEnvironmentPreparedEvent
|
||||
↓
|
||||
ApplicationContextInitializedEvent
|
||||
↓
|
||||
ApplicationPreparedEvent
|
||||
↓
|
||||
ContextRefreshedEvent
|
||||
↓
|
||||
ApplicationReadyEvent
|
||||
```
|
||||
|
||||
  当所有的Bean实例化、依赖注入和初始化回调都执行完成后,Spring容器会发布ContextRefreshedEvent事件。这个事件标志着Spring IoC容器已经完全刷新并准备就绪。
|
||||
|
||||
**🔹 步骤7.2 - 命令行运行器执行**
|
||||
运行器类型与特点:
|
||||
| 运行器接口 | 执行方法 | 参数类型 | 使用场景 |
|
||||
| - | - | - | - |
|
||||
| ApplicationRunner | run(ApplicationArguments) | 封装的应用参数 | 需要丰富参数信息时 |
|
||||
| CommandLineRunner | run(String... args) | 原始字符串参数 | 简单参数处理时 |
|
||||
|
||||
  Spring Boot会执行所有实现了ApplicationRunner或CommandLineRunner接口的Bean。
|
||||
|
||||
**🔹 步骤7.3 - 健康检查与指标收集**
|
||||
启动后的监控机制:
|
||||
1. ❤️ 健康检查:通过HealthIndicator监控应用健康状态
|
||||
2. 📊 应用指标:通过Micrometer收集运行时指标
|
||||
3. 🔔 事件监听:监控应用生命周期事件
|
||||
4. 📝 日志记录:记录启动完成状态和耗时
|
||||
77
docs/Web/SpringBoot/SpringBoot3-GraalVM.md
Normal file
77
docs/Web/SpringBoot/SpringBoot3-GraalVM.md
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: SpringBoot3原生镜像
|
||||
date: 2025-11-27
|
||||
---
|
||||
|
||||
# 一、原生镜像
|
||||
  传统 Java 应用基于 JVM 运行,需要加载完整的类库和 JVM 运行时环境,导致启动时间长、内存占用高。而**原生镜像技术通过提前编译(AOT)将 Java 应用直接编译为本地机器码,无需 JVM 即可运行**,具有以下核心优势:
|
||||
|
||||
1. 启动速度极快:毫秒级启动,相比传统 JVM 应用提升 10-100 倍、
|
||||
2. 内存占用低:仅包含应用运行所需的最小资源集合
|
||||
3. 部署体积小:通常为传统 JAR 包的 1/10 左右
|
||||
4. 适合云原生场景:尤其适合 Kubernetes 等容器化环境
|
||||
|
||||
# 二、安装使用
|
||||
## 2.1 基础软件
|
||||
1. GraalVM(类似于JAVA JDK)下载:[官网](https://www.graalvm.org/downloads/),下载完成后需要配置环境变量
|
||||
2. native-image安装:cmd运行:gu install native-image
|
||||
3. Visual Studio Community2022 选择:使用C++的桌面开发
|
||||
|
||||
## 2.2 结合Springboot3 项目
|
||||
1. IDEA创建Springboot3项目
|
||||
2. Project Structure选择GraalVM JDK
|
||||
3. 配置pom打包
|
||||
```xml
|
||||
<plugins>
|
||||
<!-- 将项目打包成可执行的 JAR/WAR 文件 -->
|
||||
<!-- repackage目标会替换 Maven 默认的打包行为 -->
|
||||
<!-- 使 JAR/WAR 文件包含所有依赖项,成为一个独立运行的应用 -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
<!-- 执行 AOT 编译 -->
|
||||
<goal>process-aot</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- GraalVM Native Maven 插件 -->
|
||||
<plugin>
|
||||
<groupId>org.graalvm.buildtools</groupId>
|
||||
<artifactId>native-maven-plugin</artifactId>
|
||||
<version>0.10.2</version>
|
||||
<configuration>
|
||||
<buildArgs combine.children="append">
|
||||
<!-- 支持 http 协议 -->
|
||||
<buildArg>--enable-url-protocols=http</buildArg>
|
||||
<!-- 用于解决数据库中文乱码问题 -->
|
||||
<buildArg>-H:+AddAllCharsets</buildArg>
|
||||
</buildArgs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-native</id>
|
||||
<goals>
|
||||
<goal>compile-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
```
|
||||
4. 选择Maven->Lifecycle->clean和package,或者输入命令:mvn -Pnative native:compile
|
||||
5. 在target目录下可以看到生成的.exe文件,打开运行即可。
|
||||
|
||||
## 三、注意事项
|
||||
  实际体验下来发现主要存在以下几个问题
|
||||
1. 编译打包的速度非常慢
|
||||
2. 目前有些第三方库并不支持(Mybatis已支持,Mybatis Plus暂不支持)
|
||||
|
||||
# 四、参考资料
|
||||
1. [使用 Spring Boot 和 GraalVM 构建原生镜像](https://springdoc.cn/spring-native-intro/)
|
||||
2. [GraalVM 原生镜像支持](https://docs.springframework.org.cn/spring-boot/reference/native-image/introducing-graalvm-native-images.html)
|
||||
65
docs/Web/SpringBoot/SpringBootBean.md
Normal file
65
docs/Web/SpringBoot/SpringBootBean.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: SpringBoot Bean简介
|
||||
date: 2025-11-29
|
||||
---
|
||||
|
||||
# 一、Bean概念
|
||||
## 1.1 定义
|
||||
  Spring bean是Spring框架在运行时管理的对象,Bean是一个由Spring IoC容器实例化、组装和管理的对象。
|
||||
  一个 Bean 可以是任何普通的 Java 对象,例如 POJO、Service、Respository、Controller 等等。将一个类声明为 Bean 的方式可以是在类级别上使用 `@Component` 注解或其派生注解(`@Service`、`@Repository`、`@Controller`等),也可以是通过配置文件进行显式的声明。
|
||||
|
||||
## 1.2 实例化
|
||||
  Spring 容器负责实例化 Bean。当应用程序启动时,Spring 容器会根据配置信息或注解扫描的结果,找到并实例化所有被标记为 Bean 的类,并将它们加入容器中。实例化的过程由 Spring 的 IoC 容器负责。
|
||||
|
||||
## 1.3 管理
|
||||
  一旦 Bean 被实例化,Spring 容器将负责管理 Bean 的生命周期和依赖关系。它会根据配置文件或注解的信息,自动解决 Bean 之间的依赖关系,确保在需要的时候正确的注入依赖。Spring 容器还会负责销毁不再需要的 Bean。
|
||||
|
||||
## 1.4 依赖注入
|
||||
  依赖注入是 Spring 框架的一个重要特性,它允许通过自动或显式配置的方式将 Bean 的依赖项注入到其它 Bean 中。依赖注入可以通过构造函数注入、Setter 方法注入或字段注入的方式实现,其中最常见的是使用 `@Autowired`注解进行注入。
|
||||
|
||||
## 1.5 作用域
|
||||
  Spring 框架提供了多种作用域(scope)来管理 Bean 的生命周期。常见的作用域包括单例(Singleton)、原型(Prototype)、会话(Session)、请求(Request)等。默认情况下,Bean 是单例的,即每个容器中只存在一个实例。但可以根据需要配置其它作用域。
|
||||
|
||||
## 1.6 自动装配
|
||||
  Spring Boot 支持自动装配(Auto - wiring),它能够根据类型或名称自动解析和注入依赖关系。通过在需要注入的字段、构造函数或 Setter 方法上使用 `@Autowired` 注解,Spring 容器会自动查找并注入对应的 Bean。
|
||||
|
||||
# 二、Bean生命周期
|
||||

|
||||
|
||||
## 2.1 实例化
|
||||
  指创建对象的过程,也就是说,当 Spring 容器启动时,它会扫描所有的配置文件,读取 Bean 定义,然后根据定义创建 Bean 对象的实例。这个过程中,Spring 容器会使用反射机制来调用 Bean 的构造函数来实例化对象。
|
||||
|
||||
## 2.2 属性赋值
|
||||
  对 Bean 的实例化对象进行属性填充;
|
||||
|
||||
## 2.3 初始化
|
||||
  指 Bean 对象被创建后,Spring 容器会根据配置文件中的定义来对其进行一系列的属性设置、依赖注入等操作,使其可以正常运行。这个过程中,Spring 容器会调用 Bean 的一些特定方法,如 set 方法、init-method 方法等来完成对象的初始化。
|
||||
|
||||
## 2.4 销毁
|
||||
  执行一些预定义的销毁方法;
|
||||
|
||||
# 三、Bean注解——@Component
|
||||
## 基本概念
|
||||
  @Component 是 Spring 框架中的一个注解,用于将类标记为 Spring 管理的组件。当一个类被 @Component 注解修饰时,Spring 会在启动时自动扫描并将该类实例化为 Bean,放入应用程序的上下文(ApplicationContext)中,以便在其他地方可以通过依赖注入使用。
|
||||
|
||||
## 主要作用
|
||||
1. 组件扫描:Spring 通过组件扫描机制自动发现并注册被 @Component 注解的类。
|
||||
2. Bean 定义:被注解的类会被视为 Spring Bean,由 Spring 容器管理其生命周期。
|
||||
3. 依赖注入:其他组件可以通过注解(如 @Autowired)注入这些 Bean。
|
||||
|
||||
## 其他衍生注解
|
||||
| 注解 | 所属层 | 继承关系 | 适用场景 |
|
||||
|------------|------------|-------------|----------|
|
||||
| @Component | 通用组件 | 基础注解 | 不适合特定层的组件(如工具类、配置类) |
|
||||
| @Repository | 数据访问层 (DAO) | 继承 @Component | 数据库操作类、ORM 映射类(如 JPA Repository) |
|
||||
| @Service | 服务层 (Service) | 继承 @Component | 业务逻辑处理(如订单处理、用户认证) |
|
||||
| @Controller | 控制层 (MVC/Web) | 继承 @Component | 处理 HTTP 请求的控制器(如 REST API、Web 页面控制器) |
|
||||
| @RestController | 控制层 (REST API) | 继承 @Controller | RESTful API 接口开发 |
|
||||
|
||||
| 注解 | 功能描述 | Spring 自动处理特性 |
|
||||
|------------------|----------------------|-------------------|
|
||||
| @Component | 标记类为 Spring Bean,通过组件扫描自动注册。<br>无特定语义,是其他组件注解的基础。 | - Bean 生命周期管理<br>- 依赖注入支持 |
|
||||
| @Repository | 标记数据访问组件(如数据库操作类),<br>自动处理数据访问异常。 | - 异常转换<br>- DAO 组件扫描 |
|
||||
| @Service | 标记业务逻辑组件,负责处理业务流程、事务管理等。<br>提供语义化,明确该类在架构中的角色。 | - 与 @Transactional 注解默认集成<br>- IDE 识别为服务层组件 |
|
||||
| @Controller | 标记 Web 控制器,处理 HTTP 请求。<br>与 Spring MVC 框架紧密集成。 | - 请求映射自动注册<br>- 支持 @RequestMapping、@GetMapping 等注解<br>- 视图解析(返回视图名称或 JSON) |
|
||||
| @RestController | @Controller + @ResponseBody 的组合,专门用于 RESTful API,直接返回 JSON/XML 等数据。 | - 所有方法默认返回 JSON/XML(无需每个方法添加 @ResponseBody) |
|
||||
110
docs/Web/SpringBoot/SpringBootStarter.md
Normal file
110
docs/Web/SpringBoot/SpringBootStarter.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
title: SpringBoot Starter原理
|
||||
date: 2025-11-27
|
||||
---
|
||||
|
||||
# 一、简介
|
||||
  Spring Boot Starter是一组预定义的依赖项集合,旨在简化Maven或Gradle等构建工具中的依赖管理。每个Starter都包含了实现特定功能所需的库和组件,以及相应的配置文件。开发者只需在项目中引入相应的Starter依赖,即可快速搭建起具备该功能的项目骨架。
|
||||
  Starter=依赖+自动配置+配置文件
|
||||
|
||||
# 二、实现原理
|
||||
## 2.1 传统实现
|
||||
  例如引入Spring中的jpa,则需要以下步骤:
|
||||
1. 在Maven中引入数据库依赖
|
||||
2. 在Maven中引入jpa依赖
|
||||
3. 在配置文件中配置属性
|
||||
4. 调试程序
|
||||
|
||||
  每次新建项目都需要重复此流程,操作繁琐。
|
||||
|
||||
## 2.2 自定义Starter实现
|
||||
1. 新建Maven项目,在pom.xml文件中定义需要的依赖项。
|
||||
2. 创建自动配置类 AutoConfigurationTest,添加@configuration注解,使其能够被SpringBoot自动扫描到。
|
||||
3. 添加自动装配机制,在src/main/resources/META-INF文件夹下创建spring.factories文件,添加以下配置:
|
||||
|
||||
```bash
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.example.springbootstartercustom.AutoConfigurationTest
|
||||
```
|
||||
|
||||
  注:这里文件夹和文件名一定要正确,因此SpringFactoriesLoader中就是这么定义的。
|
||||
|
||||
4. 在配置文件中自定义属性(可选)。
|
||||
5. 安装打包到maven仓库中。
|
||||
6. 其他项目通过pom.xml文件引入该starter。
|
||||
|
||||
## 2.3 Starter实现原理
|
||||
  加载依赖->扫描自动配置类->加载配置文件
|
||||
|
||||
# 三、高级特性
|
||||
## 3.1 可插拔Starter
|
||||
  所谓可插拔就是可以自行决定是否需要加载该starter的功能。例如可以通过注解的方式决定是否加载。
|
||||
|
||||
1. 定义注解
|
||||
|
||||
```java
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EnableAutoConfigTest {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
2. 在自动配置类 AutoConfiguration中增加条件注解
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@ConditionalOnBean(annotation = EnableAutoConfigTest.class)
|
||||
public class AutoConfigurationTest {
|
||||
}
|
||||
```
|
||||
|
||||
3. 在相应位置添加@EnableAutoConfigTest注解该stater才会生效。
|
||||
|
||||
## 3.2 自定义配置文件
|
||||
  所谓自定义配置文件,就是可以在引入stater后,可以通过修改配置文件覆盖原来的配置属性,从而灵活配置stater功能。
|
||||
|
||||
1. 引用spring-boot-configuration-processor
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2. 定义Properties配置类
|
||||
|
||||
```java
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
public class TestProperties {
|
||||
private String name = "test";
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 在自动配置类 AutoConfigurationTest中引用
|
||||
|
||||
```java
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import({TestProperties.class})
|
||||
public class AutoConfigurationTest {
|
||||
@Resource
|
||||
private TestProperties testProperties;
|
||||
}
|
||||
```
|
||||
|
||||
4. 在引入的stater工程中,修改配置文件:
|
||||
|
||||
```yaml
|
||||
test:
|
||||
name: test1
|
||||
```
|
||||
561
docs/Web/SpringBoot/SpringIOC.md
Normal file
561
docs/Web/SpringBoot/SpringIOC.md
Normal file
@@ -0,0 +1,561 @@
|
||||
---
|
||||
title: Spring IOC简介
|
||||
date: 2025-11-26
|
||||
---
|
||||
|
||||
# 一、IOC
|
||||
  Spring框架的核心是IOC(控制反转)容器,它负责管理应用程序中的对象(称为Bean)的创建、配置和组装。
|
||||
|
||||
# 二、Bean
|
||||
## 2.1 定义
|
||||
  在Spring中,Bean是指由Spring IOC容器管理的对象实例。这些对象不再由应用程序直接创建,而是由容器负责实例化、配置和组装。
|
||||
  例如@Service、@Controller就是特殊的Bean。
|
||||
|
||||
## 2.2 特点
|
||||
- 由容器管理:生命周期由Spring容器控制
|
||||
- 可配置性:通过配置元数据定义Bean的行为
|
||||
- 依赖注入:容器自动处理Bean之间的依赖关系
|
||||
- 作用域支持:支持singleton、prototype等不同作用域
|
||||
|
||||
## 2.3 创建方式
|
||||
```java
|
||||
// 1. 通过@Component注解
|
||||
@Component
|
||||
public class UserService {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 2. 通过@Bean注解(配置类中)
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new DataSource();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 通过XML配置
|
||||
<bean id="userService" class="com.example.UserService"/>
|
||||
```
|
||||
|
||||
::: tip
|
||||
@Service、@Controller这些注解是@Component的衍生注解。
|
||||
@Component注解可以通过@Autowired自动注入,一般用于自己编写的业务模块,需要配合@ComponentScan启用组件扫描(实际开发中@SpringBootApplication注解已经包含了@ComponentScan注解)。
|
||||
@Bean注解一般用于注册第三方库的类。
|
||||
:::
|
||||
|
||||
## 2.4 作用域
|
||||
  控制 Bean 在容器中的实例数量和生命周期。Spring 提供多种作用域,默认是 singleton。通过 @Scope 注解指定,如 @Scope("prototype")。
|
||||
- singleton(单例):容器中只有一个实例,所有请求共享该实例(默认值)。
|
||||
- prototype(多例):每次请求(如 getBean())都会创建新实例,容器不管理其销毁。
|
||||
- request(Web):每个 HTTP 请求创建一个实例,仅在当前请求有效。
|
||||
- session(Web):每个会话创建一个实例,在会话有效期内有效。
|
||||
|
||||
## 2.5 生命周期
|
||||
1. 实例化(Instantiation)
|
||||
容器通过构造器创建 Bean 实例(分配内存)。
|
||||
|
||||
2. 属性注入(Population)
|
||||
容器将依赖的 Bean 注入到当前 Bean 的字段或方法(如 @Autowired 标注的依赖)。
|
||||
|
||||
3. 初始化前(Post-processing before initialization)
|
||||
执行 BeanPostProcessor 的 postProcessBeforeInitialization 方法(AOP 代理生成在此阶段)。
|
||||
|
||||
4. 初始化(Initialization)
|
||||
调用 @PostConstruct 注解的方法(JSR-250 标准,推荐)。
|
||||
若实现 InitializingBean 接口,调用 afterPropertiesSet() 方法。
|
||||
执行自定义初始化方法(如 @Bean(initMethod = "init") 中指定的 init 方法)。
|
||||
|
||||
5. 初始化后(Post-processing after initialization)
|
||||
执行 BeanPostProcessor 的 postProcessAfterInitialization 方法。
|
||||
|
||||
6. 使用(In Use)
|
||||
Bean 被应用程序调用。
|
||||
|
||||
7. 销毁(Destruction)
|
||||
调用 @PreDestroy 注解的方法(JSR-250 标准,推荐)。
|
||||
若实现 DisposableBean 接口,调用 destroy() 方法。
|
||||
执行自定义销毁方法(如 @Bean(destroyMethod = "destroy") 中指定的 destroy 方法)。
|
||||

|
||||
|
||||
# 三、BeanDefinition
|
||||
## 3.1 定义
|
||||
  BeanDefinition是Spring框架中描述Bean的元数据对象,它包含了创建Bean实例所需的所有信息。
|
||||
|
||||
## 3.2 作用
|
||||
- 定义Bean的类名
|
||||
- 设置Bean的作用域
|
||||
- 配置Bean的属性值
|
||||
- 定义依赖关系
|
||||
- 配置初始化/销毁方法
|
||||
|
||||
## 3.3 结构
|
||||
```xml
|
||||
BeanDefinition (接口)
|
||||
↑
|
||||
AbstractBeanDefinition (抽象类)
|
||||
↑
|
||||
RootBeanDefinition, GenericBeanDefinition, ChildBeanDefinition
|
||||
```
|
||||
|
||||
## 3.4 关键属性
|
||||
```java
|
||||
public interface BeanDefinition {
|
||||
// Bean的类名
|
||||
void setBeanClassName(String beanClassName);
|
||||
String getBeanClassName();
|
||||
|
||||
// 作用域(singleton、prototype等)
|
||||
void setScope(String scope);
|
||||
String getScope();
|
||||
|
||||
// 是否延迟初始化
|
||||
void setLazyInit(boolean lazyInit);
|
||||
boolean isLazyInit();
|
||||
|
||||
// 依赖的Bean名称
|
||||
void setDependsOn(String... dependsOn);
|
||||
String[] getDependsOn();
|
||||
|
||||
// 初始化方法
|
||||
void setInitMethodName(String initMethodName);
|
||||
String getInitMethodName();
|
||||
|
||||
// 销毁方法
|
||||
void setDestroyMethodName(String destroyMethodName);
|
||||
String getDestroyMethodName();
|
||||
|
||||
// 工厂方法名
|
||||
void setFactoryMethodName(String factoryMethodName);
|
||||
String getFactoryMethodName();
|
||||
}
|
||||
```
|
||||
|
||||
## 3.5 创建过程
|
||||
```java
|
||||
// 编程方式创建BeanDefinition
|
||||
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
|
||||
beanDefinition.setBeanClassName("com.example.UserService");
|
||||
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
|
||||
beanDefinition.setLazyInit(false);
|
||||
|
||||
// 注册到BeanFactory
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerBeanDefinition("userService", beanDefinition);
|
||||
```
|
||||
|
||||
# 四、BeanFactory
|
||||
## 4.1 定义
|
||||
  BeanFactory是Spring IOC容器的根接口,提供了最基本的IOC功能。它是访问Spring容器的基本客户端视图。
|
||||
|
||||
## 4.2 结构
|
||||
```xml
|
||||
BeanFactory (根接口)
|
||||
↑
|
||||
ListableBeanFactory, HierarchicalBeanFactory, AutowireCapableBeanFactory
|
||||
↑
|
||||
ConfigurableBeanFactory, ConfigurableListableBeanFactory
|
||||
↑
|
||||
DefaultListableBeanFactory (主要实现类)
|
||||
```
|
||||
|
||||
## 4.3 核心方法
|
||||
```java
|
||||
public interface BeanFactory {
|
||||
// 根据名称获取Bean实例
|
||||
Object getBean(String name) throws BeansException;
|
||||
|
||||
// 根据名称和类型获取Bean实例
|
||||
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
|
||||
|
||||
// 根据类型获取Bean实例
|
||||
<T> T getBean(Class<T> requiredType) throws BeansException;
|
||||
|
||||
// 判断是否包含指定名称的Bean
|
||||
boolean containsBean(String name);
|
||||
|
||||
// 判断Bean是否是单例
|
||||
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
|
||||
|
||||
// 判断Bean是否是原型
|
||||
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
|
||||
|
||||
// 获取Bean的类型
|
||||
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
|
||||
}
|
||||
```
|
||||
|
||||
## 4.4 使用示例
|
||||
```java
|
||||
// 创建BeanFactory实例
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
// 创建BeanDefinition
|
||||
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
|
||||
beanDefinition.setBeanClassName("com.example.UserService");
|
||||
|
||||
// 注册BeanDefinition
|
||||
beanFactory.registerBeanDefinition("userService", beanDefinition);
|
||||
|
||||
// 获取Bean实例
|
||||
UserService userService = beanFactory.getBean("userService", UserService.class);
|
||||
```
|
||||
|
||||
## 4.5 特点
|
||||
- 轻量级:功能相对简单,资源消耗少
|
||||
- 延迟加载:默认在第一次请求时才创建Bean实例
|
||||
- 基础功能:提供最基本的IOC功能
|
||||
|
||||
# 五、ApplicationContext ★★★
|
||||
## 5.1 定义
|
||||
  ApplicationContext是BeanFactory的子接口,在BeanFactory的基础上提供了更多企业级功能,是Spring容器的完整实现。
|
||||
|
||||
## 5.2 结构
|
||||
```xml
|
||||
BeanFactory
|
||||
↑
|
||||
ApplicationContext (扩展接口)
|
||||
↑
|
||||
ConfigurableApplicationContext
|
||||
↑
|
||||
AbstractApplicationContext
|
||||
↑
|
||||
ClassPathXmlApplicationContext, FileSystemXmlApplicationContext,
|
||||
AnnotationConfigApplicationContext, WebApplicationContext
|
||||
```
|
||||
|
||||
## 5.3 特点
|
||||
- 完整的IOC功能:包含BeanFactory的所有功能
|
||||
- 预实例化单例Bean:默认在启动时创建单例Bean
|
||||
- 企业级功能:支持AOP、事务管理、国际化等
|
||||
- 资源访问:统一的资源访问接口
|
||||
- 事件机制:支持应用内事件发布/订阅
|
||||
|
||||
## 5.4 refresh方法
|
||||
```java
|
||||
// AbstractApplicationContext.java
|
||||
/**
|
||||
* Spring ApplicationContext 的核心启动方法
|
||||
* 定义了容器初始化的完整生命周期
|
||||
*/
|
||||
public void refresh() throws BeansException, IllegalStateException {
|
||||
// 使用同步锁确保容器启动的线程安全性,防止并发刷新
|
||||
synchronized (this.startupShutdownMonitor) {
|
||||
|
||||
// [1] 准备刷新容器 - 设置启动状态,验证环境配置
|
||||
prepareRefresh();
|
||||
|
||||
// [2] 获取或创建BeanFactory - 创建DefaultListableBeanFactory实例
|
||||
// 如果是XML配置,会在这里加载和解析XML文件
|
||||
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
|
||||
|
||||
// [3] 配置BeanFactory - 设置类加载器、注册核心BeanPostProcessor等
|
||||
prepareBeanFactory(beanFactory);
|
||||
|
||||
try {
|
||||
// [4] 后处理BeanFactory - 模板方法,允许子类进行特殊配置
|
||||
// Web应用会在这里注册Servlet相关的Scope
|
||||
postProcessBeanFactory(beanFactory);
|
||||
|
||||
// [5] 🚀 关键步骤:调用BeanFactory后置处理器
|
||||
// 执行BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor
|
||||
// ConfigurationClassPostProcessor在这里扫描@Component、@Configuration等注解
|
||||
invokeBeanFactoryPostProcessors(beanFactory);
|
||||
|
||||
// [6] 注册Bean后置处理器 - 注册所有BeanPostProcessor
|
||||
// 包括AOP、事务、自定义的BeanPostProcessor,此时只是注册,还未执行
|
||||
registerBeanPostProcessors(beanFactory);
|
||||
|
||||
// [7] 初始化消息源 - 设置国际化消息资源
|
||||
// 用于支持多语言消息解析
|
||||
initMessageSource();
|
||||
|
||||
// [8] 初始化应用事件广播器 - 创建事件发布机制的核心组件
|
||||
// 用于发布ApplicationEvent事件
|
||||
initApplicationEventMulticaster();
|
||||
|
||||
// [9] 模板方法:子类可以在这里执行特殊的刷新逻辑
|
||||
// SpringBoot在这里启动内嵌的Tomcat服务器
|
||||
onRefresh();
|
||||
|
||||
// [10] 注册监听器 - 将所有ApplicationListener注册到事件广播器
|
||||
// 包括静态指定的监听器和从BeanFactory中获取的监听器
|
||||
registerListeners();
|
||||
|
||||
// [11] 🏭 核心步骤:完成BeanFactory的初始化
|
||||
// 实例化所有非懒加载的单例Bean,执行依赖注入、AOP代理等
|
||||
finishBeanFactoryInitialization(beanFactory);
|
||||
|
||||
// [12] 完成刷新过程 - 发布ContextRefreshedEvent事件
|
||||
// 标记容器启动完成,可以正常工作了
|
||||
finishRefresh();
|
||||
|
||||
} catch (BeansException ex) {
|
||||
// 如果刷新过程中出现异常,需要销毁已经创建的单例Bean,防止资源泄漏
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Exception encountered during context initialization - " +
|
||||
"cancelling refresh attempt: " + ex);
|
||||
}
|
||||
|
||||
// 销毁已经创建的单例Bean
|
||||
destroyBeans();
|
||||
|
||||
// 重置容器状态为未激活
|
||||
cancelRefresh(ex);
|
||||
|
||||
// 重新抛出异常
|
||||
throw ex;
|
||||
} finally {
|
||||
// 最后重置一些公共的缓存,比如反射缓存
|
||||
resetCommonCaches();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4.1 prepareRefresh()
|
||||
```java
|
||||
protected void prepareRefresh() {
|
||||
this.startupDate = System.currentTimeMillis();
|
||||
this.closed.set(false);
|
||||
this.active.set(true);
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Refreshing " + this);
|
||||
}
|
||||
|
||||
initPropertySources();
|
||||
getEnvironment().validateRequiredProperties();
|
||||
this.earlyApplicationEvents = new LinkedHashSet<>();
|
||||
}
|
||||
```
|
||||
|
||||
- 这是容器启动的第一步,主要做初始化准备工作
|
||||
- 设置容器的启动时间戳和活跃状态
|
||||
- 验证必要的环境属性是否配置正确(比如通过@Value注解标记为必须的属性)
|
||||
- 初始化早期事件集合,用于存储在事件多播器创建前发布的事件
|
||||
- 为整个刷新过程奠定基础状态
|
||||
|
||||
### 5.4.2 obtainFreshBeanFactory()
|
||||
```java
|
||||
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
|
||||
refreshBeanFactory();
|
||||
return getBeanFactory();
|
||||
}
|
||||
|
||||
// 子类实现示例
|
||||
protected final void refreshBeanFactory() throws IllegalStateException {
|
||||
if (hasBeanFactory()) {
|
||||
destroyBeans();
|
||||
closeBeanFactory();
|
||||
}
|
||||
try {
|
||||
DefaultListableBeanFactory beanFactory = createBeanFactory();
|
||||
customizeBeanFactory(beanFactory);
|
||||
loadBeanDefinitions(beanFactory);
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new ApplicationContextException("I/O error", ex);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 创建或刷新底层的BeanFactory(Spring容器的核心工厂)
|
||||
- 如果是刷新操作,会先销毁旧的BeanFactory和所有Bean实例
|
||||
- 创建新的DefaultListableBeanFactory实例
|
||||
- 加载Bean定义(从XML文件或注解配置中解析Bean的元数据)
|
||||
- 这个步骤完成后,容器就知道了有哪些Bean需要管理,但还没有创建Bean实例
|
||||
|
||||
### 5.4.3 prepareBeanFactory()
|
||||
```java
|
||||
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
|
||||
beanFactory.setBeanClassLoader(getClassLoader());
|
||||
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver());
|
||||
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
|
||||
|
||||
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
|
||||
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
|
||||
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
|
||||
|
||||
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
|
||||
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
|
||||
}
|
||||
```
|
||||
|
||||
- 配置BeanFactory的基本设置和功能组件
|
||||
- 设置类加载器、SPEL表达式解析器、属性编辑器
|
||||
- 注册ApplicationContextAwareProcessor,这是实现各种Aware接口的关键
|
||||
- 忽略某些接口的依赖注入(这些接口由容器特殊处理)
|
||||
- 注册可解析的依赖关系,当Bean需要这些类型时直接返回容器实例
|
||||
- 为BeanFactory添加基本的功能支持
|
||||
|
||||
### 5.4.4 postProcessBeanFactory()
|
||||
```java
|
||||
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
|
||||
// 空方法 - 模板方法模式
|
||||
// 子类可以覆盖此方法添加特定逻辑
|
||||
}
|
||||
```
|
||||
|
||||
- 模板方法,允许子类在标准初始化后对BeanFactory进行自定义配置
|
||||
- 在Web环境中,会在这里注册Servlet相关的Scope(request、session等)
|
||||
- 可以添加特定环境的BeanPostProcessor或进行其他特殊配置
|
||||
- 这是框架扩展的重要切入点
|
||||
|
||||
### 5.4.5 invokeBeanFactoryPostProcessors()
|
||||
```java
|
||||
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
|
||||
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
|
||||
}
|
||||
```
|
||||
|
||||
- 🚀 这是最关键的一步,负责扫描和注册所有的Bean定义
|
||||
- 执行所有BeanFactoryPostProcessor,它们可以修改Bean的定义信息
|
||||
- 特别是ConfigurationClassPostProcessor,它负责:
|
||||
扫描@Component、@Service、@Repository等注解
|
||||
处理@Configuration配置类
|
||||
解析@Bean方法定义
|
||||
处理@Import、@ComponentScan等注解
|
||||
- 这一步完成后,所有的Bean定义都已经被解析和注册
|
||||
|
||||
### 5.4.6 registerBeanPostProcessors()
|
||||
```java
|
||||
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
|
||||
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
|
||||
}
|
||||
```
|
||||
|
||||
- 注册所有的BeanPostProcessor到BeanFactory中
|
||||
- 包括AOP、事务管理、自定义的Bean后置处理器等
|
||||
- 注意:此时只是注册,真正的调用要等到Bean实例化时
|
||||
- 这些处理器按照优先级顺序注册,确保执行顺序正确
|
||||
- 这是Spring AOP和事务管理等高级功能的基础
|
||||
|
||||
### 5.4.7 initMessageSource()
|
||||
```java
|
||||
protected void initMessageSource() {
|
||||
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
|
||||
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
|
||||
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
|
||||
}
|
||||
else {
|
||||
DelegatingMessageSource dms = new DelegatingMessageSource();
|
||||
this.messageSource = dms;
|
||||
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 初始化国际化消息支持
|
||||
- 如果用户配置了自定义的MessageSource,就使用用户的配置
|
||||
- 否则创建默认的DelegatingMessageSource
|
||||
- 支持从属性文件读取多语言消息,实现国际化功能
|
||||
- 用于解析${}占位符和国际化消息
|
||||
|
||||
### 5.4.8 initApplicationEventMulticaster()
|
||||
```java
|
||||
protected void initApplicationEventMulticaster() {
|
||||
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
|
||||
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
|
||||
this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
|
||||
}
|
||||
else {
|
||||
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
|
||||
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 初始化应用事件发布机制的核心组件
|
||||
- 如果用户配置了自定义的事件广播器,就使用用户的配置
|
||||
- 否则创建默认的SimpleApplicationEventMulticaster
|
||||
- 这个组件负责将应用事件广播给所有监听器
|
||||
- 支持观察者模式,实现应用内组件间的解耦通信
|
||||
|
||||
### 5.4.9 onRefresh() - 刷新时扩展点
|
||||
```java
|
||||
protected void onRefresh() throws BeansException {
|
||||
// 空方法 - 模板方法
|
||||
}
|
||||
```
|
||||
|
||||
- 模板方法,允许子类在特定时机执行自定义逻辑
|
||||
- 在Spring Boot中,这里会启动内嵌的Web服务器(Tomcat、Jetty等)
|
||||
- 在Spring MVC中,这里会初始化HandlerMapping、HandlerAdapter等组件
|
||||
- 这是框架扩展的重要切入点,不同的ApplicationContext实现有不同的行为
|
||||
|
||||
### 5.4.10 registerListeners()
|
||||
```java
|
||||
protected void registerListeners() {
|
||||
for (ApplicationListener<?> listener : getApplicationListeners()) {
|
||||
getApplicationEventMulticaster().addApplicationListener(listener);
|
||||
}
|
||||
|
||||
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
|
||||
for (String listenerBeanName : listenerBeanNames) {
|
||||
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
|
||||
}
|
||||
|
||||
// 发布早期事件
|
||||
if (this.earlyApplicationEvents != null) {
|
||||
for (ApplicationEvent earlyEvent : this.earlyApplicationEvents) {
|
||||
getApplicationEventMulticaster().multicastEvent(earlyEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 注册所有的事件监听器到事件广播器
|
||||
- 包括静态注册的监听器和从BeanFactory中获取的监听器Bean
|
||||
- 发布在监听器注册前收集的早期事件
|
||||
- 确保事件发布机制完整可用,应用事件能够正确分发
|
||||
|
||||
### 5.4.11 finishBeanFactoryInitialization()
|
||||
```java
|
||||
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
|
||||
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME)) {
|
||||
beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
|
||||
}
|
||||
|
||||
if (!beanFactory.hasEmbeddedValueResolver()) {
|
||||
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
|
||||
}
|
||||
|
||||
beanFactory.freezeConfiguration();
|
||||
beanFactory.preInstantiateSingletons();
|
||||
}
|
||||
```
|
||||
|
||||
- 🏭 这是最核心的步骤,真正创建Bean实例
|
||||
- 初始化类型转换服务(ConversionService)
|
||||
- 注册嵌入式值解析器(用于解析${}占位符)
|
||||
- 冻结Bean定义,防止后续修改
|
||||
- 实例化所有非懒加载的单例Bean,这个过程中会:
|
||||
创建Bean实例
|
||||
执行依赖注入(@Autowired、@Resource等)
|
||||
执行BeanPostProcessor(AOP代理在这里发生)
|
||||
调用初始化方法(@PostConstruct、InitializingBean)
|
||||
- 这是容器启动过程中最耗时的步骤
|
||||
|
||||
### 5.4.12 finishRefresh()
|
||||
```java
|
||||
protected void finishRefresh() {
|
||||
clearResourceCaches();
|
||||
initLifecycleProcessor();
|
||||
getLifecycleProcessor().onRefresh();
|
||||
publishEvent(new ContextRefreshedEvent(this));
|
||||
LiveBeansView.registerApplicationContext(this);
|
||||
}
|
||||
```
|
||||
|
||||
- 完成容器的启动过程,进行最后的清理和通知工作
|
||||
- 清除资源缓存,释放临时资源
|
||||
- 初始化生命周期处理器,管理Bean的生命周期
|
||||
- 发布ContextRefreshedEvent事件,通知所有监听器容器已启动完成
|
||||
- 注册LiveBeansView(用于监控和调试)
|
||||
- 标记整个容器启动流程结束,容器进入就绪状态
|
||||
|
||||
### 5.4.13 总结
|
||||
  refresh()方法按固定顺序执行12个步骤,核心可归纳为:**准备容器环境 → 解析并注册Bean定义→ 初始化事件、消息等基础设施 → 实例化所有非懒加载的单例Bean→ 完成启动并发布事件**。整个过程通过模板方法模式定义,确保了扩展性和一致性。
|
||||
  核心在于两个关键扩展机制:**BeanFactoryPostProcessor在Bean定义阶段介入,负责修改和注册Bean的定义信息;BeanPostProcessor在Bean实例化阶段介入,负责对创建好的Bean实例进行增强和包装。**
|
||||
94
docs/Web/SpringBoot/SpringMVC.md
Normal file
94
docs/Web/SpringBoot/SpringMVC.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: Spring MVC简介
|
||||
date: 2025-11-27
|
||||
---
|
||||
|
||||
# 一、基础概念
|
||||
  Spring MVC(Spring Model-View-Controller)是 Spring Framework 中的一部分,它是一个基于 **请求驱动** 的 Web 框架,主要用于构建 Web 应用程序,并且遵循 MVC(模型-视图-控制器) 设计模式。它将 Web 应用的业务逻辑、用户界面和请求处理分离,使得代码更加模块化和可维护。
|
||||
|
||||
# 二、具体流程
|
||||

|
||||
|
||||
1. 客户端发起HTTP请求
|
||||
|
||||
  客户端(浏览器、API 客户端等)向服务器发送 HTTP 请求。请求包含了目标 URL、HTTP 方法(GET、POST 等)以及可能的请求参数。
|
||||
前端通过发送 HTTP 请求(通常是 AJAX 请求)到后端的 API 端点,并期望获得响应(通常是 JSON 数据)。
|
||||
|
||||
2. 请求到达 DispatcherServlet
|
||||
|
||||
  所有 HTTP 请求都会先到达 Spring MVC 的核心组件:**DispatcherServlet**。它是前端控制器,负责将请求分发给正确的处理器。DispatcherServlet 是所有请求的入口,Spring Boot 会自动配置它。
|
||||
  DispatcherServlet 接受所有的 HTTP 请求,根据请求 URL 和 HTTP 方法,定位匹配的控制器方法,将请求交给相应的 Handler Mapping 来匹配控制器。在请求处理前后,执行 拦截器(可选)。最终通过 View Resolver 返回一个合适的视图(对于 RESTful API,通常返回 JSON 格式的数据)。
|
||||
|
||||
3. 查找处理器(Handler Mapping)
|
||||
|
||||
  DispatcherServlet 根据请求的 URL 和 HTTP 方法,查找与之匹配的处理器方法(通常是一个控制器中的方法)。这是由 HandlerMapping 完成的。Spring MVC 提供了多种方式来映射 URL 到控制器方法,例如 @RequestMapping、@GetMapping 等注解。
|
||||
  @RestController:该注解标记的类会返回 JSON 数据。
|
||||
  @RequestMapping:用于映射 URL 路径和 HTTP 方法。
|
||||
  @GetMapping、@PostMapping 等:简化版本的 @RequestMapping。
|
||||
|
||||
4. 执行拦截器(Interceptor)
|
||||
|
||||
  在请求到达控制器之前,DispatcherServlet 会依次执行配置的拦截器。拦截器主要用于日志记录、安全检查、请求参数的预处理等。
|
||||
  preHandle:请求进入控制器方法之前。
|
||||
  postHandle:请求完成后,视图渲染之前。
|
||||
  afterCompletion:视图渲染完成后。
|
||||
  **如果拦截器的 preHandle 方法返回 false,请求会被拦截,后续的处理不会继续进行。**
|
||||
|
||||
5. 控制器方法处理请求(Handler Execution)
|
||||
|
||||
  DispatcherServlet 调用合适的控制器方法来处理请求。控制器方法负责处理请求的业务逻辑,并返回一个 Java 对象,这个对象会被自动转换成 JSON 格式返回给客户端。
|
||||
  Spring 通过 @ResponseBody 注解(或 @RestController)实现将 Java 对象自动转换为 JSON 格式。默认情况下,Spring 使用 Jackson 来将 Java 对象序列化成 JSON 数据。
|
||||
  @RestController:将控制器标记为返回 JSON 数据的 RESTful 控制器。
|
||||
  @ResponseBody:标记方法返回的数据需要直接作为响应体返回,而不是解析为视图。
|
||||
  @RequestParam:用于获取单个请求参数。
|
||||
  @PathVariable:用于从 URL 中提取变量(如 /users/{id} 中的 {id})。
|
||||
|
||||
6. 响应返回客户端
|
||||
|
||||
  DispatcherServlet 将处理完的结果(JSON 数据)返回给客户端。响应数据通常会包含:
|
||||
  **状态码**:如 200 OK(成功)、201 Created(资源创建成功)、404 Not Found(资源未找到)、500 Internal Server Error(服务器错误)等。
|
||||
  **响应头**:通常包括 Content-Type: application/json,表示响应体是 JSON 格式。
|
||||
  **响应体**:包含返回的数据,通常是一个 JSON 格式的对象或数组。
|
||||
|
||||
注:以上只适用于基于RESTful API 前后端分离的架构。
|
||||
|
||||
# 三、完整流程
|
||||

|
||||
  在SpringMVC前面还有Tomcat容器层。客户端发起请求后,先经过Tomcat容器,根据请求的 URL 确定该请求是否是静态资源请求(如 .html、.css、.js)。如果是静态资源请求,Tomcat 会直接返回文件,而不经过 Spring MVC 的请求处理链。对于动态请求(如请求 /user 等),Tomcat 会将这些请求交给 DispatcherServlet 进行处理。
|
||||
  在进入DispatcherServlet前可以增加过滤器进行处理。过滤器不属于Spring部分。
|
||||
  Tomcat 是一个轻量级的 Web 服务器和 Servlet 容器,支持 Java Servlet 和 JSP 规范,能够处理 HTTP 请求并生成动态 Web 内容。
|
||||
  Servlet 是 Java 编写的服务器端程序,能够接收 HTTP 请求并生成响应,它是动态 Web 内容生成的核心技术。
|
||||
  Tomcat 管理 Servlet 的生命周期,并负责处理客户端的请求和返回响应。
|
||||
|
||||
# 四、重要知识点
|
||||
## 4.1 拦截器和过滤器区别
|
||||
1. 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
|
||||
2. 拦截器只能对action请求(DispatcherServlet 映射的请求)起作用,而过滤器则可以对几乎所有的请求起作用。
|
||||
3. 拦截器可以访问容器中的Bean(DI),而过滤器不能访问(基于spring注册的过滤器也可以访问容器中的bean)。
|
||||
4. 执行顺序:过滤器->拦截器
|
||||
|
||||
## 4.2 拦截器执行顺序
|
||||
  HandlerInterceptor 拦截器执行顺序:
|
||||
1. preHandle():请求处理前 按注册顺序依次执行。
|
||||
2. Controller:请求处理 请求到达Controller并被处理。
|
||||
3. postHandle():请求处理后,视图渲染前 按注册顺序逆序执行。
|
||||
4. afterCompletion():视图渲染后 按注册顺序逆序执行。
|
||||
|
||||
## 4.3 请求和响应体拦截器
|
||||
  RequestBodyAdvice, ResponseBodyAdvice主要发生在Controller执行前后:
|
||||
1. preHandle():请求处理前
|
||||
2. beforeBodyRead:请求体反序列化前
|
||||
3. @RequestBody:Controller方法参数绑定
|
||||
4. Controller:请求处理
|
||||
5. beforeBodyWrite():响应体序列化之前
|
||||
6. postHandle():请求处理后
|
||||
7. 视图渲染
|
||||
8. afterCompletion():请求结束
|
||||
|
||||
## 4.4 @Controller 和 @RestController区别
|
||||
  @Controller:用于定义一个 Spring MVC 控制器类。它通常用于返回视图(如 JSP 或 Thymeleaf)。
|
||||
  @RestController:是 @Controller 和 @ResponseBody 的组合,用于开发 RESTful API。它将方法的返回值直接写入 HTTP 响应体中,通常返回 JSON 或 XML 格式的数据。
|
||||
|
||||
## 4.5 异常处理
|
||||
  @ExceptionHandler:用于控制器类或方法中处理特定异常。它会捕获指定的异常并返回相应的响应(如错误页面、错误消息等)。
|
||||
  @ControllerAdvice:是一个全局异常处理器,用于捕获应用中所有控制器抛出的异常。
|
||||
Reference in New Issue
Block a user