- 创建Spring AOP详细学习笔记,包含基础概念、使用方式、切入点表达式等 - 创建Spring AOP自定义注解详解,包含实战案例和最佳实践 - 涵盖日志、权限、缓存、限流、重试等常见应用场景 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
971 lines
27 KiB
Markdown
971 lines
27 KiB
Markdown
# 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中非常强大和优雅的编程方式,能够让代码更加模块化、可维护和可扩展。 |