Spring AOP通知类型与实战示例讲解
更新时间:2024年11月18日 12:14:59 作者:lzz的编码时刻
Spring AOP提供了五种通知类型:@Before、@After、@AfterReturning、@AfterThrowing和@Around,每种通知类型都有其特定的使用场景和实现方式,通过合理使用这些通知类型,可以实现各种横切关注点的模块化和解耦,感兴趣的朋友跟随小编一起看看吧
1. @Before 前置通知
1.1 基本说明
- 在目标方法执行前执行
- 不能阻止目标方法执行(除非抛出异常)
- 可以获取目标方法的参数信息
1.2 实现示例
@Aspect @Component public class SecurityAspect { @Before("@annotation(requiresAuth)") public void checkAuth(JoinPoint joinPoint, RequiresAuth requiresAuth) { // 获取当前用户信息 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String token = request.getHeader("Authorization"); // 验证token if (!tokenService.isValid(token)) { throw new UnauthorizedException("无效的认证令牌"); } // 检查权限 String requiredRole = requiresAuth.role(); if (!hasRole(token, requiredRole)) { throw new ForbiddenException("权限不足"); } } }
1.3 典型应用场景
- 权限验证
- 参数验证
- 日志记录
- 事务开始标记
- 缓存预处理
1.4 获取参数
1.4.1 基本参数获取
@Before("execution(* com.example.service.*.*(..))") public void beforeAdvice(JoinPoint joinPoint) { // 获取方法参数 Object[] args = joinPoint.getArgs(); // 获取方法签名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String methodName = signature.getName(); // 获取参数名称 String[] parameterNames = signature.getParameterNames(); // 获取参数类型 Class<?>[] parameterTypes = signature.getParameterTypes(); // 打印参数信息 for (int i = 0; i < args.length; i++) { logger.info("Parameter {} ({}) = {}", parameterNames[i], parameterTypes[i].getSimpleName(), args[i]); } }
1.4.2 获取注解参数
@Before("@annotation(logParams)") public void beforeWithAnnotation(JoinPoint joinPoint, LogParams logParams) { // 直接获取注解属性 String description = logParams.description(); boolean logResult = logParams.logResult(); // 获取方法参数 Object[] args = joinPoint.getArgs(); // 根据注解配置记录日志 if (logParams.includeParameters()) { Arrays.stream(args) .forEach(arg -> logger.info("Parameter value: {}", arg)); } }
2. @After 后置通知
2.1 基本说明
- 在目标方法执行后执行(无论是否抛出异常)
- 不能访问目标方法的返回值
- 主要用于清理资源或类似的收尾工作
2.2 实现示例
@Aspect @Component public class ResourceCleanupAspect { @After("execution(* com.example.service.FileService.*(..))") public void cleanup(JoinPoint joinPoint) { try { // 清理临时文件 String methodName = joinPoint.getSignature().getName(); logger.info("Cleaning up resources after method: {}", methodName); cleanupTempFiles(); // 释放其他资源 releaseResources(); } catch (Exception e) { logger.error("Cleanup failed", e); } } private void cleanupTempFiles() { // 清理临时文件的具体实现 } private void releaseResources() { // 释放资源的具体实现 } }
2.3 典型应用场景
- 资源清理
- 连接关闭
- 计数器更新
- 日志记录
- 性能监控结束标记
2.4 参数获取
@After("execution(* com.example.service.*.*(..)) && args(id,name,..)") public void afterAdvice(JoinPoint joinPoint, Long id, String name) { // 直接使用参数 logger.info("Method executed with ID: {} and name: {}", id, name); // 获取目标类信息 Class<?> targetClass = joinPoint.getTarget().getClass(); // 获取代理类信息 Class<?> proxyClass = joinPoint.getThis().getClass(); }
3. @AfterReturning 返回通知
3.1 基本说明
- 在目标方法成功执行后执行
- 可以访问目标方法的返回值
- 可以修改返回值(通过包装类)
3.2 实现示例
@Aspect @Component public class ResponseHandlerAspect { @AfterReturning( pointcut = "execution(* com.example.controller.*.*(..))", returning = "result" ) public void handleResponse(JoinPoint joinPoint, Object result) { if (result instanceof List) { // 对集合类型结果进行脱敏处理 List<?> list = (List<?>) result; for (Object item : list) { if (item instanceof UserDTO) { UserDTO user = (UserDTO) item; user.setPhone(maskPhoneNumber(user.getPhone())); user.setEmail(maskEmail(user.getEmail())); } } } } private String maskPhoneNumber(String phone) { // 手机号码脱敏逻辑 return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); } private String maskEmail(String email) { // 邮箱脱敏逻辑 return email.replaceAll("(\\w{3})\\w+(@\\w+\\.\\w+)", "$1***$2"); } }
3.3 典型应用场景
- 返回值修改(如数据脱敏)
- 统计方法成功率
- 缓存结果
- 结果格式化
- 数据集合包装
4. @AfterThrowing 异常通知
4.1 基本说明
- 在目标方法抛出异常时执行
- 可以访问抛出的异常信息
- 可以进行异常转换或处理
4.2 实现示例
@Aspect @Component public class ExceptionHandlerAspect { @AfterThrowing( pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex" ) public void handleException(JoinPoint joinPoint, Exception ex) { String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); // 记录详细错误信息 logger.error("Exception in {}.{}: {}", className, methodName, ex.getMessage()); // 发送告警 if (ex instanceof DataAccessException) { alertService.sendDatabaseAlert(className, methodName, ex); } // 异常分类统计 metricService.incrementExceptionCounter(className, methodName, ex.getClass().getSimpleName()); // 如果需要,可以转换异常类型 if (ex instanceof SQLException) { throw new DatabaseException("数据库操作失败", ex); } } }
4.3 典型应用场景
- 异常记录
- 异常转换
- 告警通知
- 失败重试
- 错误统计
5. @Around 环绕通知
5.1 基本说明
- 最强大的通知类型,可以完全控制目标方法的执行
- 可以在方法执行前后添加自定义行为
- 可以修改方法的参数和返回值
- 可以决定是否执行目标方法
5.2 实现示例
@Aspect @Component public class CacheAspect { @Autowired private CacheManager cacheManager; @Around("@annotation(cacheable)") public Object handleCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable { // 构建缓存key String key = buildCacheKey(joinPoint, cacheable); // 尝试从缓存获取 Object cachedValue = cacheManager.get(key); if (cachedValue != null) { logger.debug("Cache hit for key: {}", key); return cachedValue; } // 执行目标方法 long startTime = System.currentTimeMillis(); Object result = null; try { result = joinPoint.proceed(); // 记录执行时间 long executionTime = System.currentTimeMillis() - startTime; logger.debug("Method execution time: {}ms", executionTime); // 如果执行时间超过阈值,发送告警 if (executionTime > 1000) { alertService.sendPerformanceAlert(joinPoint, executionTime); } } catch (Exception e) { // 异常处理 logger.error("Method execution failed", e); throw e; } // 将结果放入缓存 if (result != null) { cacheManager.put(key, result, cacheable.ttl()); } return result; } private String buildCacheKey(ProceedingJoinPoint joinPoint, Cacheable cacheable) { // 缓存key构建逻辑 StringBuilder key = new StringBuilder(); key.append(joinPoint.getSignature().getDeclaringTypeName()) .append(".") .append(joinPoint.getSignature().getName()); Object[] args = joinPoint.getArgs(); if (args != null && args.length > 0) { key.append(":"); for (Object arg : args) { key.append(arg != null ? arg.toString() : "null").append(","); } } return key.toString(); } }
5.3 典型应用场景
- 方法缓存
- 性能监控
- 事务处理
- 重试机制
- 并发控制
- 限流处理
@Aspect @Component public class RateLimiterAspect { private final RateLimiter rateLimiter = RateLimiter.create(100.0); // 每秒100个请求 @Around("@annotation(rateLimited)") public Object limitRate(ProceedingJoinPoint joinPoint, RateLimited rateLimited) throws Throwable { if (!rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) { throw new TooManyRequestsException("请求过于频繁,请稍后重试"); } return joinPoint.proceed(); } }
6. 最佳实践
- 选择合适的通知类型
- 如果只需要前置处理,用@Before
- 如果需要访问返回值,用@AfterReturning
- 如果需要处理异常,用@AfterThrowing
- 如果需要完全控制方法执行,用@Around
性能考虑
- 避免在通知中执行耗时操作
- 合理使用缓存
- 注意异常处理的性能影响
代码组织
- 每个切面专注于单一职责
- 通知方法保持简洁
- 复用共同的切入点表达式
异常处理
- 在通知中要做好异常处理
- 不要吞掉异常
- 适当转换异常类型
到此这篇关于Spring AOP通知类型详解与实战的文章就介绍到这了,更多相关Spring AOP通知类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringBoot图文并茂详解如何引入mybatis与连接Mysql数据库
这篇文章主要介绍了SpringBoot如何引入mybatis与连接Mysql数据库,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2022-07-07
最新评论