添加Spring AOP学习笔记

- 创建Spring AOP详细学习笔记,包含基础概念、使用方式、切入点表达式等
- 创建Spring AOP自定义注解详解,包含实战案例和最佳实践
- 涵盖日志、权限、缓存、限流、重试等常见应用场景

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root 2025-06-26 01:15:22 +08:00
parent af08782474
commit 2a876d1d38
2 changed files with 1640 additions and 0 deletions

669
Spring-AOP-学习笔记.md Normal file
View File

@ -0,0 +1,669 @@
# Spring AOP 详细学习笔记
## 一、AOP基础概念
### 1.1 什么是AOP
AOPAspect-Oriented Programming面向切面编程是一种编程范式它通过将横切关注点cross-cutting concerns从业务逻辑中分离出来实现了关注点的模块化。
### 1.2 AOP的核心概念
#### 1.2.1 切面Aspect
切面是横切关注点的模块化,包含了通知和切入点的定义。
#### 1.2.2 连接点Join Point
程序执行过程中的某个特定点如方法调用、方法执行、字段访问等。在Spring AOP中连接点总是方法的执行。
#### 1.2.3 通知Advice
切面在特定连接点执行的动作。主要有以下类型:
- **前置通知Before**:在目标方法执行前执行
- **后置通知After**:在目标方法执行后执行(无论是否异常)
- **返回通知After Returning**:在目标方法正常返回后执行
- **异常通知After Throwing**:在目标方法抛出异常后执行
- **环绕通知Around**:包围目标方法,可以在方法执行前后都执行自定义逻辑
#### 1.2.4 切入点Pointcut
用于匹配连接点的谓词表达式。通过切入点表达式,我们可以指定哪些方法需要被增强。
#### 1.2.5 引入Introduction
允许向现有类添加新方法或属性。
#### 1.2.6 目标对象Target Object
被一个或多个切面通知的对象。
#### 1.2.7 AOP代理AOP Proxy
AOP框架创建的对象用于实现切面契约。在Spring中AOP代理可以是JDK动态代理或CGLIB代理。
#### 1.2.8 织入Weaving
将切面应用到目标对象并创建代理对象的过程。
### 1.3 Spring AOP的特点
- 基于代理的AOP实现
- 只支持方法级别的连接点
- 与Spring IoC容器完美集成
- 支持基于注解和XML的配置方式
## 二、Spring AOP的使用方式
### 2.1 引入依赖
```xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
```
### 2.2 启用AOP
#### 注解方式启用
```java
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
// 配置类内容
}
```
#### XML方式启用
```xml
<aop:aspectj-autoproxy/>
```
## 三、基于注解的AOP
### 3.1 定义切面类
```java
@Aspect
@Component
public class LoggingAspect {
// 定义切入点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 前置通知
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("执行方法: " + joinPoint.getSignature().getName());
System.out.println("参数: " + Arrays.toString(joinPoint.getArgs()));
}
// 后置通知
@After("serviceLayer()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法执行完毕: " + joinPoint.getSignature().getName());
}
// 返回通知
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("方法返回值: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "serviceLayer()", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("方法异常: " + error.getMessage());
}
// 环绕通知
@Around("serviceLayer()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知 - 方法执行前");
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("方法执行时间: " + (endTime - startTime) + "ms");
return result;
} catch (Throwable e) {
System.out.println("环绕通知 - 捕获异常: " + e.getMessage());
throw e;
}
}
}
```
### 3.2 示例服务类
```java
@Service
public class UserService {
public User findUserById(Long id) {
System.out.println("查找用户: " + id);
return new User(id, "张三");
}
public void saveUser(User user) {
System.out.println("保存用户: " + user.getName());
}
public void deleteUser(Long id) {
System.out.println("删除用户: " + id);
if (id == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
}
}
```
## 四、切入点表达式详解
### 4.1 execution表达式
最常用的切入点表达式,用于匹配方法执行。
语法:`execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)`
示例:
```java
// 匹配所有public方法
@Pointcut("execution(public * *(..))")
// 匹配指定包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
// 匹配指定类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
// 匹配所有返回值为void的方法
@Pointcut("execution(void *(..))")
// 匹配第一个参数为Long类型的方法
@Pointcut("execution(* *(Long,..))")
// 匹配所有以save开头的方法
@Pointcut("execution(* save*(..))")
```
### 4.2 within表达式
限制匹配特定类型内的连接点。
```java
// 匹配指定包下的所有方法
@Pointcut("within(com.example.service.*)")
// 匹配指定类的所有方法
@Pointcut("within(com.example.service.UserService)")
```
### 4.3 @annotation表达式
匹配带有特定注解的方法。
```java
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
String value() default "";
}
// 切入点表达式
@Pointcut("@annotation(com.example.annotation.Loggable)")
public void loggableMethods() {}
// 使用注解
@Service
public class OrderService {
@Loggable("创建订单")
public void createOrder(Order order) {
// 业务逻辑
}
}
```
### 4.4 @within表达式
匹配特定注解标注的类内的所有方法。
```java
@Pointcut("@within(org.springframework.stereotype.Service)")
public void allServiceMethods() {}
```
### 4.5 args表达式
匹配特定参数类型的方法。
```java
// 匹配只有一个String参数的方法
@Pointcut("args(String)")
// 匹配第一个参数为String的方法
@Pointcut("args(String,..)")
```
### 4.6 组合切入点表达式
使用&&、||、!组合多个切入点表达式。
```java
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(Loggable)")
public void serviceLoggableMethods() {}
@Pointcut("within(com.example.service.*) || within(com.example.dao.*)")
public void serviceOrDaoMethods() {}
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))")
public void nonGetterServiceMethods() {}
```
## 五、通知参数传递
### 5.1 获取方法参数
```java
@Aspect
@Component
public class ParameterAspect {
@Before("execution(* com.example.service.*.*(..)) && args(id,name,..)")
public void logWithParams(Long id, String name) {
System.out.println("方法参数 - ID: " + id + ", Name: " + name);
}
@Around("@annotation(loggable)")
public Object logWithAnnotation(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
System.out.println("注解值: " + loggable.value());
return joinPoint.proceed();
}
}
```
### 5.2 获取目标对象和代理对象
```java
@Before("execution(* com.example.service.*.*(..))")
public void logTarget(JoinPoint joinPoint) {
Object target = joinPoint.getTarget(); // 目标对象
Object proxy = joinPoint.getThis(); // 代理对象
System.out.println("目标类: " + target.getClass().getName());
}
```
## 六、基于XML的AOP配置
### 6.1 XML配置示例
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义切面Bean -->
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
<!-- AOP配置 -->
<aop:config>
<!-- 定义切入点 -->
<aop:pointcut id="serviceOperation"
expression="execution(* com.example.service.*.*(..))"/>
<!-- 定义切面 -->
<aop:aspect ref="loggingAspect">
<!-- 前置通知 -->
<aop:before method="logBefore" pointcut-ref="serviceOperation"/>
<!-- 后置通知 -->
<aop:after method="logAfter" pointcut-ref="serviceOperation"/>
<!-- 返回通知 -->
<aop:after-returning method="logAfterReturning"
returning="result"
pointcut-ref="serviceOperation"/>
<!-- 异常通知 -->
<aop:after-throwing method="logAfterThrowing"
throwing="error"
pointcut-ref="serviceOperation"/>
<!-- 环绕通知 -->
<aop:around method="logAround" pointcut-ref="serviceOperation"/>
</aop:aspect>
</aop:config>
</beans>
```
### 6.2 XML配置的切面类
```java
public class LoggingAspect {
public void logBefore(JoinPoint joinPoint) {
System.out.println("XML配置 - 前置通知: " + joinPoint.getSignature().getName());
}
public void logAfter(JoinPoint joinPoint) {
System.out.println("XML配置 - 后置通知: " + joinPoint.getSignature().getName());
}
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("XML配置 - 返回通知: " + result);
}
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("XML配置 - 异常通知: " + error.getMessage());
}
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("XML配置 - 环绕通知开始");
Object result = joinPoint.proceed();
System.out.println("XML配置 - 环绕通知结束");
return result;
}
}
```
## 七、实际应用场景
### 7.1 日志记录
```java
@Aspect
@Component
@Slf4j
public class LogAspect {
@Around("@annotation(Log)")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("开始执行方法: {}, 参数: {}", methodName, Arrays.toString(args));
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long costTime = System.currentTimeMillis() - startTime;
log.info("方法执行成功: {}, 耗时: {}ms, 返回值: {}", methodName, costTime, result);
return result;
} catch (Throwable e) {
long costTime = System.currentTimeMillis() - startTime;
log.error("方法执行失败: {}, 耗时: {}ms, 异常: {}", methodName, costTime, e.getMessage());
throw e;
}
}
}
```
### 7.2 权限控制
```java
@Aspect
@Component
public class SecurityAspect {
@Autowired
private SecurityService securityService;
@Before("@annotation(RequiresPermission)")
public void checkPermission(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
RequiresPermission permission = signature.getMethod().getAnnotation(RequiresPermission.class);
if (!securityService.hasPermission(permission.value())) {
throw new UnauthorizedException("没有权限执行此操作: " + permission.value());
}
}
}
// 使用示例
@Service
public class AdminService {
@RequiresPermission("admin:user:delete")
public void deleteUser(Long userId) {
// 删除用户逻辑
}
}
```
### 7.3 缓存处理
```java
@Aspect
@Component
public class CacheAspect {
private Map<String, Object> cache = new ConcurrentHashMap<>();
@Around("@annotation(Cacheable)")
public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
String key = generateKey(joinPoint);
if (cache.containsKey(key)) {
System.out.println("从缓存获取数据: " + key);
return cache.get(key);
}
Object result = joinPoint.proceed();
cache.put(key, result);
System.out.println("将数据放入缓存: " + key);
return result;
}
private String generateKey(ProceedingJoinPoint joinPoint) {
return joinPoint.getSignature().toString() + Arrays.toString(joinPoint.getArgs());
}
}
```
### 7.4 事务管理
```java
@Aspect
@Component
public class TransactionAspect {
@Autowired
private TransactionManager transactionManager;
@Around("@annotation(Transactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
transactionManager.beginTransaction();
try {
Object result = joinPoint.proceed();
transactionManager.commit();
return result;
} catch (Exception e) {
transactionManager.rollback();
throw e;
}
}
}
```
### 7.5 性能监控
```java
@Aspect
@Component
public class PerformanceAspect {
private static final int SLOW_EXECUTION_THRESHOLD = 1000; // 1秒
@Around("@annotation(MonitorPerformance)")
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
long startTime = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long executionTime = System.currentTimeMillis() - startTime;
if (executionTime > SLOW_EXECUTION_THRESHOLD) {
log.warn("方法执行缓慢: {} 耗时 {}ms", methodName, executionTime);
// 可以发送告警通知
}
// 记录性能指标
recordMetrics(methodName, executionTime);
}
}
private void recordMetrics(String methodName, long executionTime) {
// 将性能数据发送到监控系统
}
}
```
### 7.6 重试机制
```java
@Aspect
@Component
public class RetryAspect {
@Around("@annotation(Retry)")
public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Retry retry = signature.getMethod().getAnnotation(Retry.class);
int attempts = retry.attempts();
long delay = retry.delay();
Exception lastException = null;
for (int i = 0; i < attempts; i++) {
try {
return joinPoint.proceed();
} catch (Exception e) {
lastException = e;
if (i < attempts - 1) {
log.warn("方法执行失败,{}ms后重试当前第{}次尝试", delay, i + 1);
Thread.sleep(delay);
}
}
}
throw new RuntimeException("重试" + attempts + "次后仍然失败", lastException);
}
}
// 使用示例
@Service
public class EmailService {
@Retry(attempts = 3, delay = 1000)
public void sendEmail(String to, String subject, String content) {
// 发送邮件逻辑,可能会失败
}
}
```
## 八、最佳实践
### 8.1 切面优先级
当多个切面作用于同一个连接点时,可以通过@Order注解指定优先级
```java
@Aspect
@Component
@Order(1) // 数字越小,优先级越高
public class SecurityAspect {
// 安全检查逻辑
}
@Aspect
@Component
@Order(2)
public class LoggingAspect {
// 日志记录逻辑
}
```
### 8.2 避免循环依赖
切面不应该通知自己,避免无限循环:
```java
@Aspect
@Component
public class LoggingAspect {
// 排除切面类本身
@Pointcut("execution(* com.example..*.*(..)) && !within(com.example.aspect..*)")
public void applicationMethods() {}
}
```
### 8.3 合理使用通知类型
- 使用@Before进行参数验证和权限检查
- 使用@AfterReturning处理返回值
- 使用@AfterThrowing进行异常处理和告警
- 使用@Around进行性能监控和事务管理
- 简单场景避免使用@Around,因为它最强大但也最复杂
### 8.4 切入点表达式优化
- 尽量缩小切入点范围,避免过度匹配
- 使用within()限定包或类范围
- 组合使用多个表达式提高精确度
### 8.5 异常处理
在环绕通知中正确处理异常:
```java
@Around("applicationMethods()")
public Object handleException(ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} catch (BusinessException e) {
// 业务异常,记录日志后继续抛出
log.error("业务异常: {}", e.getMessage());
throw e;
} catch (Exception e) {
// 系统异常,转换为统一的异常类型
log.error("系统异常", e);
throw new SystemException("系统错误", e);
}
}
```
## 九、常见问题
### 9.1 AOP不生效的原因
1. 目标类没有被Spring管理未添加@Component等注解
2. 调用的是类内部方法self-invocation
3. 方法是private的Spring AOP只能代理public方法
4. 没有启用AOP@EnableAspectJAutoProxy
### 9.2 JDK代理 vs CGLIB代理
- JDK代理基于接口目标类必须实现接口
- CGLIB代理基于继承可以代理没有实现接口的类
配置使用CGLIB代理
```java
@EnableAspectJAutoProxy(proxyTargetClass = true)
```
### 9.3 内部方法调用问题
解决方案:
```java
@Service
public class UserService {
@Autowired
private UserService self; // 注入自己的代理对象
public void method1() {
// 通过代理对象调用AOP生效
self.method2();
}
@Transactional
public void method2() {
// 业务逻辑
}
}
```
## 十、总结
Spring AOP提供了一种优雅的方式来处理横切关注点通过本笔记的学习你应该掌握了
1. AOP的核心概念和原理
2. 基于注解和XML的AOP配置方式
3. 各种切入点表达式的使用
4. 不同类型通知的应用场景
5. 实际项目中的AOP应用案例
6. AOP的最佳实践和常见问题解决
建议通过实际编码练习来加深理解特别是在日志记录、权限控制、事务管理等场景中应用AOP。

View File

@ -0,0 +1,971 @@
# Spring AOP 自定义注解详解
## 一、为什么使用自定义注解配合AOP
### 1.1 优势
1. **代码简洁**:只需在方法上添加注解即可
2. **语义明确**:注解名称直接表达意图
3. **易于维护**:修改切面逻辑不影响业务代码
4. **灵活配置**:通过注解参数传递配置信息
5. **解耦合**:业务逻辑与横切关注点分离
### 1.2 对比传统方式
```java
// 传统方式:基于方法名匹配
@Pointcut("execution(* com.example.service.*.*(..))")
// 注解方式:更精确、更灵活
@Pointcut("@annotation(com.example.annotation.Log)")
```
## 二、自定义注解的创建
### 2.1 基本结构
```java
@Target(ElementType.METHOD) // 注解作用目标
@Retention(RetentionPolicy.RUNTIME) // 注解保留策略
@Documented // 生成文档
public @interface Log {
String value() default ""; // 注解参数
String module() default ""; // 模块名
LogLevel level() default LogLevel.INFO; // 日志级别
}
// 枚举类型
public enum LogLevel {
DEBUG, INFO, WARN, ERROR
}
```
### 2.2 元注解说明
- **@Target**:指定注解可以用在哪些地方
- ElementType.METHOD方法
- ElementType.TYPE
- ElementType.FIELD字段
- ElementType.PARAMETER参数
- **@Retention**:指定注解的生命周期
- RetentionPolicy.SOURCE源码期
- RetentionPolicy.CLASS编译期
- RetentionPolicy.RUNTIME运行期AOP必须使用
- **@Documented**注解信息包含在JavaDoc中
- **@Inherited**:子类可以继承父类的注解
## 三、完整示例:日志注解
### 3.1 创建日志注解
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
// 操作描述
String value() default "";
// 操作类型
OperationType type() default OperationType.OTHER;
// 是否保存请求参数
boolean saveParams() default true;
// 是否保存响应结果
boolean saveResult() default true;
}
// 操作类型枚举
public enum OperationType {
INSERT("新增"),
UPDATE("修改"),
DELETE("删除"),
SELECT("查询"),
EXPORT("导出"),
IMPORT("导入"),
OTHER("其他");
private final String desc;
OperationType(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
```
### 3.2 创建日志切面
```java
@Aspect
@Component
@Slf4j
public class SysLogAspect {
@Autowired
private SysLogService sysLogService;
@Autowired
private HttpServletRequest request;
// 使用@annotation匹配带有@SysLog注解的方法
@Pointcut("@annotation(com.example.annotation.SysLog)")
public void logPointCut() {}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 获取注解信息
SysLog sysLog = getAnnotation(point);
// 创建日志对象
SysLogEntity logEntity = new SysLogEntity();
logEntity.setOperation(sysLog.value());
logEntity.setType(sysLog.type().getDesc());
logEntity.setCreateTime(new Date());
// 获取请求信息
logEntity.setIp(getIpAddress(request));
logEntity.setUrl(request.getRequestURL().toString());
logEntity.setMethod(request.getMethod());
// 获取用户信息
String username = getCurrentUsername();
logEntity.setUsername(username);
// 获取方法信息
String className = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
logEntity.setClassName(className);
logEntity.setMethodName(methodName);
// 保存请求参数
if (sysLog.saveParams()) {
Object[] args = point.getArgs();
String params = JSON.toJSONString(args);
logEntity.setParams(params);
}
try {
// 执行方法
Object result = point.proceed();
// 保存响应结果
if (sysLog.saveResult() && result != null) {
String resultStr = JSON.toJSONString(result);
logEntity.setResult(resultStr);
}
// 设置执行时间
long time = System.currentTimeMillis() - beginTime;
logEntity.setTime(time);
logEntity.setStatus(1); // 成功
return result;
} catch (Throwable e) {
// 记录异常信息
logEntity.setStatus(0); // 失败
logEntity.setError(e.getMessage());
logEntity.setTime(System.currentTimeMillis() - beginTime);
throw e;
} finally {
// 异步保存日志
CompletableFuture.runAsync(() -> sysLogService.save(logEntity));
}
}
// 获取注解对象
private SysLog getAnnotation(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
return method.getAnnotation(SysLog.class);
}
// 获取IP地址
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
// 获取当前用户名(示例)
private String getCurrentUsername() {
// 从Security Context或Session中获取
return "admin";
}
}
```
### 3.3 使用示例
```java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
@SysLog(value = "新增用户", type = OperationType.INSERT)
public Result<User> createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return Result.success(savedUser);
}
@PutMapping("/{id}")
@SysLog(value = "修改用户信息", type = OperationType.UPDATE)
public Result<User> updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
User updatedUser = userService.update(user);
return Result.success(updatedUser);
}
@DeleteMapping("/{id}")
@SysLog(value = "删除用户", type = OperationType.DELETE, saveResult = false)
public Result<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return Result.success();
}
@GetMapping("/{id}")
@SysLog(value = "查询用户详情", type = OperationType.SELECT, saveParams = false)
public Result<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return Result.success(user);
}
}
```
## 四、实战案例集合
### 4.1 权限控制注解
```java
// 权限注解定义
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresPermission {
// 权限标识
String[] value();
// 逻辑关系AND 或 OR
Logical logical() default Logical.AND;
enum Logical {
AND, OR
}
}
// 角色注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresRole {
String[] value();
Logical logical() default Logical.AND;
}
// 权限切面
@Aspect
@Component
public class PermissionAspect {
@Autowired
private AuthService authService;
@Before("@annotation(permission)")
public void checkPermission(RequiresPermission permission) {
String[] requiredPerms = permission.value();
Logical logical = permission.logical();
Set<String> userPerms = authService.getCurrentUserPermissions();
boolean hasPermission = false;
if (logical == Logical.AND) {
// 需要所有权限
hasPermission = userPerms.containsAll(Arrays.asList(requiredPerms));
} else {
// 只需要其中一个权限
hasPermission = Arrays.stream(requiredPerms)
.anyMatch(userPerms::contains);
}
if (!hasPermission) {
throw new UnauthorizedException("权限不足:需要权限 " + Arrays.toString(requiredPerms));
}
}
@Before("@annotation(role)")
public void checkRole(RequiresRole role) {
String[] requiredRoles = role.value();
Set<String> userRoles = authService.getCurrentUserRoles();
boolean hasRole = Arrays.stream(requiredRoles)
.anyMatch(userRoles::contains);
if (!hasRole) {
throw new UnauthorizedException("角色不足:需要角色 " + Arrays.toString(requiredRoles));
}
}
}
// 使用示例
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/users")
@RequiresPermission("user:view")
public List<User> listUsers() {
// 查看用户列表
}
@PostMapping("/users")
@RequiresPermission({"user:create", "user:edit"})
public User createUser(@RequestBody User user) {
// 创建用户(需要同时有创建和编辑权限)
}
@DeleteMapping("/users/{id}")
@RequiresRole("ADMIN")
@RequiresPermission("user:delete")
public void deleteUser(@PathVariable Long id) {
// 删除用户(需要管理员角色和删除权限)
}
}
```
### 4.2 缓存注解
```java
// 缓存注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {
// 缓存key前缀
String prefix() default "";
// 缓存key支持SpEL表达式
String key() default "";
// 过期时间(秒)
int expire() default 300;
// 条件SpEL表达式返回true时才缓存
String condition() default "";
}
// 清除缓存注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheEvict {
String prefix() default "";
String key() default "";
boolean allEntries() default false;
}
// 缓存切面
@Aspect
@Component
public class CacheAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final SpelExpressionParser parser = new SpelExpressionParser();
@Around("@annotation(cache)")
public Object cache(ProceedingJoinPoint point, Cache cache) throws Throwable {
// 构建缓存key
String key = buildKey(point, cache.prefix(), cache.key());
// 检查条件
if (!checkCondition(point, cache.condition())) {
return point.proceed();
}
// 尝试从缓存获取
Object cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
log.debug("缓存命中: {}", key);
return cached;
}
// 执行方法
Object result = point.proceed();
// 存入缓存
if (result != null) {
redisTemplate.opsForValue().set(key, result, cache.expire(), TimeUnit.SECONDS);
log.debug("缓存存储: {}", key);
}
return result;
}
@After("@annotation(evict)")
public void evict(JoinPoint point, CacheEvict evict) {
if (evict.allEntries()) {
// 清除所有缓存
Set<String> keys = redisTemplate.keys(evict.prefix() + "*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
} else {
// 清除指定缓存
String key = buildKey(point, evict.prefix(), evict.key());
redisTemplate.delete(key);
}
}
// 构建缓存key
private String buildKey(JoinPoint point, String prefix, String key) {
if (StringUtils.isEmpty(key)) {
// 默认使用方法签名作为key
return prefix + ":" + point.getSignature().toLongString();
}
// 解析SpEL表达式
EvaluationContext context = new StandardEvaluationContext();
Method method = ((MethodSignature) point.getSignature()).getMethod();
String[] paramNames = new LocalVariableTableParameterNameDiscoverer()
.getParameterNames(method);
Object[] args = point.getArgs();
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
Expression expression = parser.parseExpression(key);
return prefix + ":" + expression.getValue(context, String.class);
}
// 检查条件
private boolean checkCondition(JoinPoint point, String condition) {
if (StringUtils.isEmpty(condition)) {
return true;
}
// 解析条件表达式
EvaluationContext context = new StandardEvaluationContext();
// ... 设置变量
Expression expression = parser.parseExpression(condition);
return Boolean.TRUE.equals(expression.getValue(context, Boolean.class));
}
}
// 使用示例
@Service
public class ProductService {
@Cache(prefix = "product", key = "#id", expire = 600)
public Product findById(Long id) {
// 从数据库查询
return productRepository.findById(id);
}
@Cache(prefix = "product", key = "#category + ':' + #page", expire = 300)
public List<Product> findByCategory(String category, int page) {
// 分页查询
return productRepository.findByCategory(category, page);
}
@CacheEvict(prefix = "product", key = "#product.id")
public void updateProduct(Product product) {
// 更新产品,清除缓存
productRepository.save(product);
}
@CacheEvict(prefix = "product", allEntries = true)
public void clearAllCache() {
// 清除所有产品缓存
}
}
```
### 4.3 限流注解
```java
// 限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
// 限流key
String key() default "";
// 时间窗口(秒)
int period() default 60;
// 限流次数
int count() default 100;
// 限流类型
LimitType limitType() default LimitType.IP;
enum LimitType {
IP, // 根据IP限流
USER, // 根据用户限流
GLOBAL, // 全局限流
CUSTOM // 自定义key
}
}
// 限流切面
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private HttpServletRequest request;
@Before("@annotation(rateLimit)")
public void limit(JoinPoint point, RateLimit rateLimit) {
String key = buildLimitKey(point, rateLimit);
// 使用Redis实现滑动窗口限流
long currentTime = System.currentTimeMillis();
long windowStart = currentTime - rateLimit.period() * 1000;
// 移除窗口外的记录
redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
// 获取窗口内的请求数
Long count = redisTemplate.opsForZSet().count(key, windowStart, currentTime);
if (count != null && count >= rateLimit.count()) {
throw new RateLimitException("访问太频繁,请稍后再试");
}
// 添加当前请求
redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), currentTime);
// 设置过期时间
redisTemplate.expire(key, rateLimit.period(), TimeUnit.SECONDS);
}
private String buildLimitKey(JoinPoint point, RateLimit rateLimit) {
StringBuilder key = new StringBuilder("rate_limit:");
switch (rateLimit.limitType()) {
case IP:
key.append(getIpAddress()).append(":");
break;
case USER:
key.append(getCurrentUserId()).append(":");
break;
case GLOBAL:
key.append("global:");
break;
case CUSTOM:
key.append(rateLimit.key()).append(":");
break;
}
// 添加方法签名
key.append(point.getSignature().toLongString());
return key.toString();
}
private String getIpAddress() {
// 获取真实IP
String ip = request.getHeader("X-Real-IP");
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
private String getCurrentUserId() {
// 从认证信息中获取用户ID
return "user123";
}
}
// 使用示例
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/search")
@RateLimit(period = 60, count = 10, limitType = LimitType.IP)
public Result search(@RequestParam String keyword) {
// IP限流每个IP每分钟最多10次
}
@PostMapping("/order")
@RateLimit(period = 3600, count = 100, limitType = LimitType.USER)
public Result createOrder(@RequestBody Order order) {
// 用户限流每个用户每小时最多100单
}
@GetMapping("/hot-deal")
@RateLimit(period = 1, count = 1000, limitType = LimitType.GLOBAL)
public Result getHotDeal() {
// 全局限流每秒最多1000次请求
}
}
```
### 4.4 重试注解
```java
// 重试注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retry {
// 重试次数
int maxAttempts() default 3;
// 重试间隔(毫秒)
long delay() default 1000;
// 指数退避
boolean exponentialBackoff() default false;
// 需要重试的异常类型
Class<? extends Throwable>[] retryFor() default {Exception.class};
// 不需要重试的异常类型
Class<? extends Throwable>[] noRetryFor() default {};
}
// 重试切面
@Aspect
@Component
@Slf4j
public class RetryAspect {
@Around("@annotation(retry)")
public Object retry(ProceedingJoinPoint point, Retry retry) throws Throwable {
int attempts = 0;
long delay = retry.delay();
while (attempts < retry.maxAttempts()) {
attempts++;
try {
return point.proceed();
} catch (Throwable e) {
// 检查是否需要重试
if (!shouldRetry(e, retry)) {
throw e;
}
if (attempts >= retry.maxAttempts()) {
log.error("重试{}次后仍然失败", retry.maxAttempts());
throw e;
}
log.warn("方法执行失败,{}ms后进行第{}次重试", delay, attempts);
Thread.sleep(delay);
// 指数退避
if (retry.exponentialBackoff()) {
delay = delay * 2;
}
}
}
throw new IllegalStateException("不应该到达这里");
}
private boolean shouldRetry(Throwable e, Retry retry) {
// 检查不需要重试的异常
for (Class<? extends Throwable> noRetryClass : retry.noRetryFor()) {
if (noRetryClass.isAssignableFrom(e.getClass())) {
return false;
}
}
// 检查需要重试的异常
for (Class<? extends Throwable> retryClass : retry.retryFor()) {
if (retryClass.isAssignableFrom(e.getClass())) {
return true;
}
}
return false;
}
}
// 使用示例
@Service
public class PaymentService {
@Retry(maxAttempts = 3, delay = 2000, retryFor = {TimeoutException.class, ConnectException.class})
public PaymentResult processPayment(PaymentRequest request) {
// 支付处理,可能超时
return paymentGateway.process(request);
}
@Retry(maxAttempts = 5, delay = 1000, exponentialBackoff = true)
public void sendNotification(String message) {
// 发送通知,使用指数退避策略
notificationService.send(message);
}
}
```
### 4.5 参数校验注解
```java
// 参数校验注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateParams {
// 是否校验请求参数
boolean validateRequest() default true;
// 是否校验响应结果
boolean validateResponse() default false;
// 校验组
Class<?>[] groups() default {};
}
// 校验切面
@Aspect
@Component
@Slf4j
public class ValidationAspect {
@Autowired
private Validator validator;
@Around("@annotation(validateParams)")
public Object validate(ProceedingJoinPoint point, ValidateParams validateParams) throws Throwable {
// 校验请求参数
if (validateParams.validateRequest()) {
Object[] args = point.getArgs();
for (Object arg : args) {
if (arg != null && !isBasicType(arg)) {
Set<ConstraintViolation<Object>> violations = validator.validate(arg, validateParams.groups());
if (!violations.isEmpty()) {
throw new ValidationException(buildErrorMessage(violations));
}
}
}
}
// 执行方法
Object result = point.proceed();
// 校验响应结果
if (validateParams.validateResponse() && result != null) {
Set<ConstraintViolation<Object>> violations = validator.validate(result, validateParams.groups());
if (!violations.isEmpty()) {
log.error("响应数据校验失败: {}", buildErrorMessage(violations));
}
}
return result;
}
private boolean isBasicType(Object obj) {
return obj instanceof String || obj instanceof Number ||
obj instanceof Boolean || obj instanceof Character;
}
private String buildErrorMessage(Set<ConstraintViolation<Object>> violations) {
return violations.stream()
.map(v -> v.getPropertyPath() + " " + v.getMessage())
.collect(Collectors.joining(", "));
}
}
// 使用示例
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
@ValidateParams(groups = {Create.class})
public Result<User> createUser(@RequestBody User user) {
// 自动校验User对象的Create组约束
return Result.success(userService.save(user));
}
@PutMapping("/{id}")
@ValidateParams(groups = {Update.class}, validateResponse = true)
public Result<User> updateUser(@PathVariable Long id, @RequestBody User user) {
// 校验请求和响应
return Result.success(userService.update(user));
}
}
// 实体类
@Data
public class User {
@Null(groups = Create.class)
@NotNull(groups = Update.class)
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
```
## 五、高级技巧
### 5.1 组合注解
```java
// 组合多个注解功能
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SysLog
@RequiresPermission
@RateLimit
public @interface SecureApi {
// 日志描述
String value() default "";
// 需要的权限
String[] permissions() default {};
// 限流配置
int rateLimit() default 100;
int ratePeriod() default 60;
}
// 使用组合注解
@PostMapping("/sensitive-operation")
@SecureApi(
value = "执行敏感操作",
permissions = {"admin:sensitive:operate"},
rateLimit = 10,
ratePeriod = 60
)
public Result sensitiveOperation(@RequestBody Request request) {
// 一个注解实现:日志记录 + 权限校验 + 限流
}
```
### 5.2 注解继承
```java
// 基础审计注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Auditable {
boolean enabled() default true;
}
// 继承审计功能的业务注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Auditable
public @interface BankingOperation {
String operationType();
RiskLevel riskLevel() default RiskLevel.LOW;
enum RiskLevel {
LOW, MEDIUM, HIGH, CRITICAL
}
}
// 统一处理所有审计注解
@Aspect
@Component
public class AuditAspect {
@Around("@annotation(auditable) || @annotation(banking)")
public Object audit(ProceedingJoinPoint point, Auditable auditable, BankingOperation banking) throws Throwable {
// 处理审计逻辑
}
}
```
### 5.3 动态注解处理
```java
@Aspect
@Component
public class DynamicAnnotationAspect {
@Around("@annotation(anno)")
public Object handleDynamically(ProceedingJoinPoint point, Annotation anno) throws Throwable {
// 动态获取注解类型
Class<? extends Annotation> annotationType = anno.annotationType();
// 根据不同注解类型执行不同逻辑
if (annotationType == Cache.class) {
return handleCache(point, (Cache) anno);
} else if (annotationType == Log.class) {
return handleLog(point, (Log) anno);
}
return point.proceed();
}
}
```
## 六、最佳实践总结
### 6.1 设计原则
1. **单一职责**:每个注解只负责一个功能
2. **语义清晰**:注解名称要表达明确意图
3. **参数简洁**:只提供必要的配置参数
4. **默认值合理**:为参数提供合理的默认值
### 6.2 性能优化
1. **异步处理**:日志等操作使用异步方式
2. **缓存结果**:避免重复计算
3. **条件判断**:提前判断是否需要执行切面逻辑
### 6.3 错误处理
1. **异常传播**:不要吞掉业务异常
2. **降级处理**:切面异常不应影响业务执行
3. **日志记录**:记录关键错误信息
### 6.4 测试建议
```java
@SpringBootTest
public class AopTest {
@MockBean
private SysLogService logService;
@Autowired
private UserService userService;
@Test
public void testLogAspect() {
// 测试日志切面
User user = new User();
userService.createUser(user);
// 验证日志服务被调用
verify(logService, times(1)).save(any(SysLogEntity.class));
}
}
```
自定义注解配合AOP是Spring中非常强大和优雅的编程方式能够让代码更加模块化、可维护和可扩展。