- 创建Spring AOP详细学习笔记,包含基础概念、使用方式、切入点表达式等 - 创建Spring AOP自定义注解详解,包含实战案例和最佳实践 - 涵盖日志、权限、缓存、限流、重试等常见应用场景 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
27 KiB
27 KiB
Spring AOP 自定义注解详解
一、为什么使用自定义注解配合AOP
1.1 优势
- 代码简洁:只需在方法上添加注解即可
- 语义明确:注解名称直接表达意图
- 易于维护:修改切面逻辑不影响业务代码
- 灵活配置:通过注解参数传递配置信息
- 解耦合:业务逻辑与横切关注点分离
1.2 对比传统方式
// 传统方式:基于方法名匹配
@Pointcut("execution(* com.example.service.*.*(..))")
// 注解方式:更精确、更灵活
@Pointcut("@annotation(com.example.annotation.Log)")
二、自定义注解的创建
2.1 基本结构
@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 创建日志注解
@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 创建日志切面
@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 使用示例
@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 权限控制注解
// 权限注解定义
@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 缓存注解
// 缓存注解
@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 限流注解
// 限流注解
@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 重试注解
// 重试注解
@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 参数校验注解
// 参数校验注解
@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 组合注解
// 组合多个注解功能
@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 注解继承
// 基础审计注解
@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 动态注解处理
@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 设计原则
- 单一职责:每个注解只负责一个功能
- 语义清晰:注解名称要表达明确意图
- 参数简洁:只提供必要的配置参数
- 默认值合理:为参数提供合理的默认值
6.2 性能优化
- 异步处理:日志等操作使用异步方式
- 缓存结果:避免重复计算
- 条件判断:提前判断是否需要执行切面逻辑
6.3 错误处理
- 异常传播:不要吞掉业务异常
- 降级处理:切面异常不应影响业务执行
- 日志记录:记录关键错误信息
6.4 测试建议
@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中非常强大和优雅的编程方式,能够让代码更加模块化、可维护和可扩展。