Web开发-后端拓展篇
写代码应该向报纸学习,在顶部,你希望有个头条,告诉你故事的主题,好让你决定是否读下去。第一段是整个故事的大纲,给出粗线条概述,但隐藏了故事的细节。接着读下去,细节渐次增加,直至你了解所有的日期、名字、引语、说法以及其他细节。
日志记录(lombok + logback)
依赖配置:
1 |
|
lombok
1 |
|
在每一个需要进行日志记录的类中,我们都需要手动创建 log
对象:
1 |
|
如果我们使用lombok,可以简化如下:
1 |
|
通过 @Slf4j
注解,lombook将在编译时,在类中自动添加log对象的创建。
日志级别
1 |
|
支持占位符:
log.info("用户ID: {}, 状态: {}", userId, status);
,避免字符串拼接的性能损耗
简单配置
Spring Boot 已经帮我们封装了常用的日志路径配置,你只需要在 application.properties
中加上:
1 |
|
Spring Boot 2.2+ 默认支持基于 Logback 的 按天切割 ,只要用
logging.file.name
或logging.file.path
配置,Logback 会自动生成:
1
2
logs/app.log
logs/app.2025-08-28.log
文件上传
前端上传数据
前端可以通过表单进行简单的文件上传:
1 |
|
关键点:
enctype="multipart/form-data"
:必须设置,否则文件不会被正确传输。<input type="file"/>
:浏览器会弹出文件选择框。method="post"
:文件上传必须用 POST。
后端接收数据
使用Spring开发的后端:
1 |
|
- 除文件外的其他参数接收方法不变。
MultipartFile /image
:Spring MVC 自动将前端name="/image"
的文件绑定到这个参数。
在一次请求完全处理结束之前,上传的文件会临时存储在目录:C:\Users\<用户名>\AppData\Local\Temp\tomcat.8080.2799433456118150306\work\Tomcat\localhost\ROOT
每个参数都对应一个文件。请求结束后这些临时文件都会被删除。所以后端需要存储文件。
本地存储文件
MultipartFile
对象中封装了上传文件的元信息:
获取文件元信息:
方法 | 返回类型 | 作用 | 典型用途 |
---|---|---|---|
getName() |
String |
获取表单字段名(即 <input name="..."> 的值) |
判断是哪个字段上传的文件 |
getOriginalFilename() |
String |
获取客户端原始文件名(可能包含扩展名) | 保留用户上传的文件名或提取扩展名 |
getContentType() |
String |
获取 MIME 类型(如 /image/png ) |
校验文件类型 |
判断与大小:
方法 | 返回类型 | 作用 | 典型用途 |
---|---|---|---|
isEmpty() |
boolean |
判断文件是否为空(无内容或未选择) | 上传前后端双重校验 |
getSize() |
long |
获取文件大小(字节) | 限制上传大小、防止恶意文件 |
获取文件内容:
方法 | 返回类型 | 作用 | 典型用途 |
---|---|---|---|
getBytes() |
byte[] |
将文件内容读成字节数组 | 存入数据库 BLOB、内存处理 |
getInputStream() |
InputStream |
获取输入流读取文件内容 | 流式处理、大文件分片上传 |
getResource() |
Resource |
将文件包装成 Spring Resource 对象 |
与 Spring 资源加载机制配合使用 |
保存文件:
方法 | 返回类型 | 作用 | 典型用途 |
---|---|---|---|
transferTo(File dest) |
void |
将文件保存到指定 File 路径 |
保存到本地磁盘 |
transferTo(Path dest) |
void |
将文件保存到指定 Path 路径 |
NIO 方式保存文件 |
文件名避免重复
使用UUID生成唯一字符串
1 |
|
输出示例:
1 |
|
每秒生成数十亿个 UUID,重复的概率也几乎为零
获取文件类型
1 |
|
拼接唯一文件名
1 |
|
推荐策略::业务前缀 + 日期目录 + UUID
1
2
3
4
5
6
String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = "avatar_" + UUID.randomUUID() + ext;
Path path = Paths.get(uploadBaseDir, datePath);
Files.createDirectories(path);
file.transferTo(path.resolve(newFileName).toFile());
文件大小限制
Spring默认限制上传的最大单个文件大小为1M。单次请求上传数据总限制为10M。
文件 :application.properties
1 |
|
参数 | 含义 | 示例 | 注意事项 |
---|---|---|---|
spring.servlet.multipart.max-file-size |
单个文件的最大允许大小 | 10MB |
超过会抛出 MaxUploadSizeExceededException |
spring.servlet.multipart.max-request-size |
单次请求允许的最大总大小 | 100MB |
适用于多文件批量上传场景 |
云服务——对象存储
配置文件
以对象存储云服务为例:
1 |
|
我们有许多配置信息直接硬编码在代码中,看似方便,但风险和隐患都很大。
- 维护困难:环境或账号变更时,需要重新改代码并重新发布。
- 多环境不便:开发、测试、生成环境的配置不同,无法灵活切换。
application.properties
首先在 application.properties
中自定义键名与值:
1 |
|
Java类注入:
1 |
|
@Value
注解常用于外部配置的属性注入,具体用法为:
1
@Value("${配置文件中的KEY}")
- static字段上使用
@Value
注解无法注入。
yml配置文件
Spring Boot 配置文件用 application.yml
或 application.properties
都可以,只保留一个就能正常运行——它们本质上是两种格式的等价表达。
核心逻辑
Spring Boot 启动时会自动读取 src/main/resources
下的
application.properties
application.yml
/application.yaml
同时存在时 :都会加载,但 properties 会覆盖 yml 中相同 key (取决于加载顺序和 profile 激活情况)。
项目中通常 统一使用一种格式 ,避免混乱和重复配置。
properties
:层级不清晰
1 |
|
yml
:结构清晰,无重复
1 |
|
基本语法
区分大小写
键名(key)对大小写敏感,例如
Port
与port
是不同的键。层级关系通过缩进表示
使用空格表示缩进,推荐两个空格或四个空格,必须统一。
禁止使用 Tab 制表符
缩进只能用空格,混用空格与 Tab 会导致解析错误。
缩进层级一致
同一层级的所有元素缩进必须完全一致。
格式错误会引发解析失败
常见错误包括:缩进不对齐、多余的空格、冒号后缺少空格等。
数据结构
对象/Map集合
定义方式 :
key: value
层级表示 :冒号后换行并缩进表示子属性
适用场景 :表示一组键值对,如配置项、实体对象
示例:
1 |
|
数组/List/Set集合
- 定义方式 :每个元素前加
-
,同级对齐 - 适用场景 :
- List :有序集合,可重复
- Set :无序集合,不重复(语法同 List,但解释方式依赖解析器) 示例:
1 |
|
@ConfigurationProperties
SpringBoot中,由Spring容器管理的类,可以由Spring自动注入在配置文件中设置的参数。只需满足一个条件:
- key值与变量名相同
applicatiton.yml
:
1 |
|
java配置属性类绑定:
1 |
|
@ConfigurationProperties(prefix = "...")
:按前缀自动映射配置文件字段@Component
自动注册到Spring容器
可选依赖
1 |
|
当你在项目中使用 @ConfigurationProperties
来绑定配置文件(如 YAML、Properties)时,这个依赖会在编译期扫描并生成属性元数据文件。
这些元数据会被 IDE(如 IntelliJ IDEA)读取,从而提供:
- 自动补全 (属性名提示)
- 类型校验
- 文档提示 (鼠标悬停显示注释)
没有它,代码功能依然能运行,但编写
application.yml
时不会有属性提示和检查,容易写错。
登录功能
基础登录功能
控制器层:
1 |
|
服务层:
1 |
|
数据库层:
1 |
|
登录校验
登录校验是认证(Authentication)环节,通常位于请求进入系统的最前端:
1 |
|
会话技术
会话:
- 用户打开浏览器访问 Web 服务器资源时,服务器会为该用户创建一个会话对象,用于在多次请求之间保持状态。
- 会话在以下情况结束:
- 浏览器关闭
- 会话超时
- 用户主动退出
- 会话在以下情况结束:
会话跟踪技术:
- 识别同一浏览器发起的多次请求,并在同一次会话中实现数据共享
会话跟踪方案:
技术类型 | 存储位置 | 特点 | 常见用途 |
---|---|---|---|
Cookie(客户端会话跟踪) | 浏览器端 | 轻量、可跨会话保存、依赖加密保证安全 | 记住用户名、偏好设置 |
Session(服务器会话跟踪) | 服务器端 | 安全性高、占用服务器资源、需依赖 Cookie/URL 传递 Session ID | 登录状态、购物车 |
Token / JWT(令牌技术) | 客户端(LocalStorage / Cookie) | 无状态、可跨服务验证、适合分布式系统 | API 鉴权、移动端登录 |
Cookie
1 |
|
优点:HTTP协议中支持的技术
缺点:不支持跨域
跨域是浏览器的同源策略(Same-Origin Policy)限制下的一个概念。
同源指的是:
- 协议 (http / https)相同
- 域名 / 主机名 / IP 相同
- 端口号 相同
只要三者中有一个不同,就算 不同源 ,也就是 跨域 。
如果前端页面和后端 API 不同源(跨域),即使后端设置了
Set-Cookie
,浏览器默认也不会保存或发送它
Session
1 |
|
Session通过在Cookie中设置 JSESSIONID
来确保客户端与Session一一对应。
优点:数据存储在服务器端,安全。
缺点:
- 服务器集群环境无法直接使用Session
- 无法跨域
令牌技术
优点:
- 解决集群环境下的认证问题
- 减轻服务端存储压力
缺点:需要自己实现
JWT
定义 :JWT 是一种紧凑(Compact)、自包含(Self-contained)的方式,用 JSON 对象在各方之间安全传输信息。
组成结构
JWT 由三部分组成,使用 .
分隔:
1 |
|
部分 | 作用 | 示例 | 编码方式 |
---|---|---|---|
Header | 描述元数据,如签名算法、类型 | {"alg":"HS256","typ":"JWT"} |
Base64Url |
Payload | 存放声明(Claims),即传递的数据 | {"sub":"1234567890","name":"Tom"} |
Base64Url |
Signature | 校验 Token 完整性与真实性 | HMACSHA256(base64UrlEncode(Header) + "." + base64UrlEncode(Payload), secret) |
- |
工作流程
生成 Token
- 服务端根据用户信息生成 Payload
- 使用指定算法(如 HS256)和密钥生成签名
- 拼接成
Header.Payload.Signature
传输 Token
- 一般放在 HTTP 请求头
Authorization: Bearer <token></token>
- 一般放在 HTTP 请求头
验证 Token
- 服务端用相同算法和密钥验证签名
- 校验通过则信任 Payload 中的信息
Java实现
引入依赖:
1 |
|
生成钥匙
1 |
|
步骤 | 作用 | 对应 JWT 部分 |
---|---|---|
setClaims(claims) |
设置业务数据(如 id、username 等) | Payload |
signWith(SignatureAlgorithm.HS256, secretString) |
指定签名算法(HS256)和密钥,用于生成签名 | Signature |
setExpiration(...) |
设置 Token 过期时间(标准字段 exp ) |
Payload |
compact() |
将 Header、Payload、Signature 进行 Base64URL 编码并拼接成最终字符串 | 整个 JWT |
打印字符串:
1 |
|
解析JWT
1 |
|
步骤 | 方法调用 | 作用 | 对应 JWT 部分 | 关键注意事项 |
---|---|---|---|---|
1 | Jwts.parser() |
创建 JWT 解析器 | - | 必须使用 parser() 而不是 builder() |
2 | setSigningKey(secretString) |
设置签名密钥,用于验证签名合法性 | Signature | 必须与生成 JWT 时的密钥完全一致 |
3 | parseClaimsJws(token) |
解析 JWT 字符串并校验签名 | Header + Payload + Signature | 签名不匹配会抛异常,说明令牌被篡改或伪造 |
4 | getBody() |
获取载荷(Claims),即业务数据 | Payload | 可直接读取自定义字段和标准字段(如 exp ) |
打印结果:
1 |
|
校验机制:
- 签名校验:
- 解析器会用
setSigningKey
提供的密钥,对 JWT 的 Header + Payload 重新计算签名,与令牌中的 Signature 比对。 - 如果不一致 → 抛出
SignatureException
,说明令牌被改动或伪造。
- 解析器会用
- 过期校验:
- 如果 Payload 中有 exp(过期时间),解析时会自动检查当前时间是否已超过 exp。
- 超时会抛出 ExpiredJwtException。
Claims对象可通过 get(key)
方法获取 Value
1 |
|
统一拦截
过滤器Filter
Filter详细介绍可以看:java-Web基础之Servlet、Filter、Listener -
定义 :Filter 是 Java Web 三大组件之一(Servlet、Filter、Listener)。
作用:在请求到达 Servlet 之前、响应返回客户端之前,对请求/响应进行拦截与处理。
特点 :
- 不直接生成响应内容
- 可实现请求与响应的预处理/后处理
- 可形参过滤链(多个Filter顺序执行)
Filter快速入门
核心接口与方法:
方法 | 触发时机 | 作用 |
---|---|---|
init(FilterConfig filterConfig) |
Filter 初始化时 | 读取配置、资源初始化 |
doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
每次请求经过 Filter 时 | 编写过滤逻辑,调用 chain.doFilter() 继续执行链 |
destroy() |
Filter 销毁时 | 释放资源 |
实现Filter接口:
1 |
|
注册Filter:在启动类上添加注解 @ServletComponentScan
1 |
|
@ServletComponentScan
会扫描@WebFilter
、@WebServlet
、@WebListener
等注解并注册到容器中。
Filter详解
执行流程
1 |
|
chain.doFilter(request, response)
是连接前处理和后处理的关键调用点。
拦截路径
常见拦截路径配置:
拦截路径类型 | urlPatterns 值 | 含义 |
---|---|---|
精确路径匹配 | /login |
仅拦截访问 /login 的请求 |
目录路径匹配 | /emps/* |
拦截 /emps 目录下的所有请求 |
全局路径匹配 | /* |
拦截所有请求路径 |
目录匹配 :
/path/*
会匹配该目录下的所有子路径,但不匹配/path
本身。
过滤器链
- 定义 :在一个 Web 应用中,可以配置多个 Filter,这些 Filter 按顺序依次执行,形成一个“过滤器链”。
- 执行顺序 :默认按照 过滤器类名(字符串)的自然排序 执行,除非通过
@Order
或FilterRegistrationBean.setOrder()
显式指定顺序。 - 作用 :实现请求的多阶段处理,例如日志记录、权限校验、数据预处理、响应包装等。
登录校验
示例代码:
1 |
|
拦截器Interceptor
- 定义 :拦截器是一种 动态拦截方法调用 的机制,类似于过滤器(Filter),但作用范围和实现方式不同。
- 作用 :在方法调用 前后 根据业务需求执行自定义逻辑。
- Spring 中的应用 :基于 动态代理 的 AOP(面向切面编程)机制实现,常用于请求预处理、权限校验、日志记录等。
对比项 | 拦截器(Interceptor) | 过滤器(Filter) |
---|---|---|
所属规范 | Spring 框架 | Servlet 规范 |
拦截范围 | Spring MVC 控制器方法 | 所有 Servlet 请求 |
依赖容器 | Spring 容器 | Web 容器 |
使用场景 | 登录校验、权限控制、性能监控、日志记录 | 编码设置、跨域处理、通用请求过滤 |
实现方式 | 实现 HandlerInterceptor 接口 |
实现 Filter 接口 |
执行顺序 | 在过滤器之后执行 | 在拦截器之前执行 |
是否依赖 Spring | 是 | 否 |
实现方式
实现 HandlerInterceptor
接口,并重写其三个方法。
方法 | 调用时机 | 返回值/参数 | 常见用途 |
---|---|---|---|
preHandle() |
控制器方法调用前 | boolean (true 放行,false 拦截) |
权限校验、参数验证、日志记录 |
postHandle() |
控制器方法调用后,视图渲染前 | 可修改 ModelAndView |
数据加工、统一添加模型数据 |
afterCompletion() |
请求完成后(视图渲染后) | 可获取异常信息 | 资源清理、异常处理、性能统计 |
1 |
|
注册拦截器
需要编写一个配置类,用于注册:Interceptor
1 |
|
拦截路径
拦截器路径模式 | 含义 | 示例匹配 | 示例不匹配 |
---|---|---|---|
/depts/** |
匹配 /depts 下的所有路径(多级) |
/depts/1 、/depts/1/2 |
/login |
/** |
匹配任意路径(多级) | /depts/1 、/emps |
/login (若被排除) |
/depts/* |
匹配 /depts 下的一级路径 |
/depts/1 |
/depts/1/2 |
/depts |
精确匹配路径 | /depts |
/depts/1 |
执行流程
登录校验
拦截器:
1 |
|
配置类:
1 |
|
异常处理
在Controller中try-catch
每一个Controller都要进行异常处理,不推荐。
全局异常处理器
在 Spring Boot 中,通过统一的异常捕获机制,将 Controller、Service、Mapper 等各层抛出的异常集中处理,避免在每个方法中重复写 try-catch,提高代码整洁度与可维护性。
示例:
1 |
|
关键注解
@RestControllerAdvice
:组合注解,等价于@ControllerAdvice + @ResponseBody
,用于全局异常处理并返回 JSON。@ControllerAdvice
:标记该类为全局控制器增强类,可拦截所有 Controller 层抛出的异常。@ResponseBody
:将返回值序列化为 JSON。@ExceptionHandler
:指定处理某类异常的方法。
创建步骤:
- 创建一个类并加上
@RestControllerAdvice
。 - 在类中定义方法,使用
@ExceptionHandler
指定要捕获的异常类型。 - 在方法中记录日志(可用
log.error
)并返回统一响应对象。
事务管理
Spring @Transactional
注解笔记
项目 | 内容 |
---|---|
注解 | @Transactional |
常用位置 | - Service 层方法上 - Service 类上 - Service 接口上 |
主要作用 | 将方法交由 Spring 进行事务管理: 1. 方法执行前开启事务 2. 成功执行后提交事务 3. 出现异常时回滚事务 |
示例:
1 |
|
事务进阶
rollbackFor
背景
- 默认回滚规则 :
Spring 事务默认只在RuntimeException
或Error
发生时回滚。
对于 受检异常(Checked Exception) ,默认不会回滚。 - 问题 :
某些业务中,受检异常(如Exception
、IOException
)也需要回滚,这时就要用rollbackFor
属性。
属性 | 类型 | 说明 | 示例 |
---|---|---|---|
rollbackFor | Class<? extends Throwable>[] | 指定遇到哪些异常类型时回滚事务 | rollbackFor = Exception.class |
示例:
1 |
|
配置方式 | 回滚异常范围 | 适用场景 |
---|---|---|
默认(无属性) | RuntimeException 及其子类 | 大多数业务逻辑异常 |
rollbackFor = Exception.class | 所有异常(受检 + 非受检) | 需要对受检异常也回滚的场景 |
rollbackFor = {IOException.class, SQLException.class} | 指定异常类型 | 精确控制回滚条件 |
事务传播行为(Propagation)
事务传播行为 :指一个事务方法调用另一个事务方法时,第二个方法应如何参与事务控制。
常用传播行为类型:
传播行为 | 说明 | 特点与适用场景 |
---|---|---|
REQUIRED(默认) | 如果当前存在事务,则加入;否则新建事务 | 绝大多数业务场景的默认选择 |
REQUIRES_NEW | 无论当前是否存在事务,都新建事务,原事务挂起 | 需要隔离执行、互不影响的操作 |
SUPPORTS | 如果当前存在事务,则加入;否则以非事务方式执行 | 可选事务场景 |
NOT_SUPPORTED | 总是以非事务方式执行,若存在事务则挂起 | 只读查询、日志记录等不需要事务的操作 |
MANDATORY | 必须在事务中执行,否则抛异常 | 强制要求调用方已有事务 |
NEVER | 必须在非事务环境执行,否则抛异常 | 禁止事务的场景 |
NESTED | 如果当前存在事务,则在嵌套事务中执行(有独立回滚点);否则新建事务 | 局部回滚需求,如批处理中的单条失败回滚 |
示例:
1 |
|
- 默认 REQUIRED :
a()
调用b()
时,b()
会加入a()
的事务,二者成一个整体,要么一起提交,要么一起回滚。 - 改为 REQUIRES_NEW :
b()
会新建事务,a()
的事务会挂起,b()
的提交/回滚互不影响a()
。
AOP
AOP (Aspect Oriented Programming,面向切面编程 / 面向方面编程)
本质:针对特定方法或特定关注点进行编程
目标:将横切关注点(如日志、性能统计、安全校验)从核心业务逻辑中分离出来,实现解耦与复用
实现方式 :在不修改源码的前提下,通过 代理模式 在运行时动态织入增强逻辑。
代理模式:注入时,不注入原始的类对象,而是注入功能增强后的代理类。
代理机制:
代理方式 适用场景 实现原理 特点 JDK 动态代理 目标类实现了接口 基于 java.lang.reflect.Proxy
生成代理类,通过InvocationHandler
调用目标方法只能代理接口方法,性能较好,生成速度快 CGLIB 动态代理 目标类没有实现接口 基于 ASM 字节码技术生成目标类的子类,并重写方法实现增强 可代理普通类,不能代理 final
类/方法,生成代理类速度稍慢但调用性能较高Spring 选择策略:
- 默认:有接口 → 使用 JDK 动态代理;无接口 → 使用 CGLIB
- 可通过
@EnableAspectJAutoProxy(proxyTargetClass = true)
强制使用 CGLIB
典型应用场景:
- 性能监控 :统计方法执行耗时
- 日志记录 :统一记录方法调用信息
- 权限控制 :在方法执行前进行权限校验
- 事务管理 :统一处理事务的开启、提交、回滚
以性能监控为例:
无AOP:
1 |
|
有AOP:
1 |
|
改进点 :
- 业务方法只关注核心逻辑
- 耗时统计交由切面统一处理
快速入门
引入依赖
1 |
|
定义切面
1 |
|
注解 | 作用 | 常用位置 | 易错点 |
---|---|---|---|
@Aspect |
声明一个类是切面类(Aspect) | 类上 | 忘记加 @Component 会导致切面不被 Spring 管理 |
@Around |
环绕通知(可在方法执行前后自定义逻辑,并可决定是否执行原方法) | 切面类方法上 | 必须调用 pjp.proceed() 才会执行原方法,否则会被拦截掉 |
AOP 核心概念
概念 | 含义 | 备注 / 易错点 |
---|---|---|
JoinPoint(连接点) | 程序执行过程中的一个点,例如方法调用、异常抛出等 | Spring AOP 仅支持方法级别的 JoinPoint(基于代理) |
Advice(通知) | 在 JoinPoint 上执行的代码逻辑 | 分为 @Before 、@After 、@AfterReturning 、@AfterThrowing 、@Around |
PointCut(切点) | 匹配 JoinPoint 的表达式规则 | 常用 execution() 、within() 、@annotation() 等 |
Aspect(切面) | 封装 PointCut 和 Advice 的模块 | 用 @Aspect 声明,通常配合 @Component |
Target(目标对象) | 被 AOP 增强的原始对象 | 业务逻辑类 |
Proxy(代理对象) | AOP 生成的代理对象,执行时替代 Target | JDK 动态代理或 CGLIB |
通知类型
注解 | 中文名称 | 执行时机 | 特点 | 易错点 |
---|---|---|---|---|
@Around |
环绕通知 | 在目标方法执行前后都会执行 | 可完全控制目标方法是否执行,可在前后添加逻辑 | 必须调用 ProceedingJoinPoint.proceed() 才会执行目标方法,否则会被拦截掉 |
@Before |
前置通知 | 在目标方法执行前执行 | 可获取方法入参,适合做权限校验、日志记录等 | 无法获取返回值 |
@After |
后置通知 | 在目标方法执行后执行(无论是否异常) | 类似 finally ,常用于资源释放 |
无法区分正常返回还是异常退出 |
@AfterReturning |
返回后通知 | 在目标方法正常返回后执行 | 可获取返回值,适合做结果处理、缓存更新等 | returning 参数名必须与方法形参名一致 |
@AfterThrowing |
异常后通知 | 在目标方法抛出异常后执行 | 可获取异常对象,适合做异常日志、告警等 | throwing 参数名必须与方法形参名一致 |
@Around 的织入逻辑需要自己调用
ProceedingJoinPoint.proceed()
来让底层的方法执行,其他注解不需要考虑原始方法执行
执行顺序:
1 |
|
抽取切入点表达式
示例:
1 |
|
- 访问修饰符可以是
private
、protected
、public
,但如果要跨类引用,必须是public
。
通知执行顺序
默认规则:
- 多个切面类的执行顺序默认按类名字母顺序排序。
推荐做法:
- 使用
@Order
注解指定优先级 - 数字越小、优先级越高、越先执行
1 |
|
切入点表达式
切入点表达式(Pointcut Expression) :用于匹配目标方法的规则表达式。
作用 :决定项目中哪些方法需要织入通知(Advice)。
常用两种表达式:
表达式 | 匹配规则 | 示例 | 说明 |
---|---|---|---|
execution(...) |
按方法签名匹配 | execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer)) |
精确匹配某个方法 |
@annotation(...) |
按方法上的注解匹配 | @annotation(com.itheima.anno.Log) |
匹配被特定注解标记的方法 |
execution表达式
1 |
|
?
表示可省略的部分- 访问修饰符 :可省略(如
public
、protected
) - throws 异常 :可省略(指方法声明的异常,而非实际抛出的异常)
- 包名:省略包名,即全局匹配类名和方法名
- 类名:省略类名,即全局匹配方法名
- 访问修饰符 :可省略(如
返回值 :必填(可用
*
表示任意类型)包名.类名.方法名 :可用
*
或..
进行通配*
:单个独立的任意符号..
:多个连续的任意符号,可以通配任意层级的包,或者任意类型、任意个数的参数
同时匹配多个没有共同点的方法:
1 |
|
根据业务需要,可以使用
&&
、||
、!
来组合比较复杂的切入点表达式。
annotation
@annotation
切入点表达式 :用于匹配方法上标注了指定注解的连接点(Join Point)。
语法:
1 |
|
- 注解全限定类名 :必须包含完整包路径,例如
com.itheima.anno.Log
- 只能匹配方法级别的注解(不能直接匹配类注解)
使用示例:
自定义注解:
1
2
3
4
5
6
7
8
9
10package com.itheima.anno;
import java.lang.annotation.*;
@Target(ElementType.METHOD) // 作用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时可反射获取
@Documented
public @interface Log {
String value() default ""; // 可选参数,用于描述日志内容
}在业务类中添加自定义注解
1
2
3
4
5
6
7
8
9
10
11
12
13@Service
public class UserService {
@Log("添加用户操作")
public void addUser(String username) {
System.out.println("执行添加用户逻辑: " + username);
}
@Log("删除用户操作")
public void deleteUser(Long id) {
System.out.println("执行删除用户逻辑: " + id);
}
}定义AOP组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24@Slf4j
@Aspect
@Component
public class LogAspect {
@Before("@annotation(com.itheima.anno.Log)")
public void before(JoinPoint joinPoint) {
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取注解
Log logAnno = method.getAnnotation(Log.class);
String desc = logAnno.value();
// 获取方法名和参数
String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
Object[] args = joinPoint.getArgs();
log.info("【AOP日志】方法调用前: {}", methodName);
log.info("【AOP日志】注解描述: {}", desc);
log.info("【AOP日志】参数: {}", args);
}
}Spring中可以通过
1
2@Autowired
private HttpServletRequest request;在任何由容器托管的类中获取
HttpServletRequest
对象
连接点
基本概念:
概念 | 说明 |
---|---|
JoinPoint | 连接点,表示程序执行过程中的某个点(如方法调用、异常抛出等)。在 Spring AOP 中,连接点通常是方法的执行。 |
ProceedingJoinPoint | JoinPoint 的子接口,专用于 @Around 环绕通知,允许显式调用 proceed() 来执行目标方法。 |
常用方法:
方法 | 返回值 | 作用 |
---|---|---|
getTarget() |
Object |
获取目标对象(被代理对象) |
getSignature().getName() |
String |
获取方法名 |
getArgs() |
Object[] |
获取方法参数数组 |
proceed() |
Object |
执行目标方法,并返回执行结果(仅 ProceedingJoinPoint 可用) |