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

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-26 01:15:22 +08:00

971 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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中非常强大和优雅的编程方式能够让代码更加模块化、可维护和可扩展。