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通知类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MyBatis-Plus中公共字段的统一处理的实现

    MyBatis-Plus中公共字段的统一处理的实现

    在开发中经常遇到多个实体类有共同的属性字段,这些字段属于公共字段,本文主要介绍了MyBatis-Plus中公共字段的统一处理的实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • 详解如何使用XML配置来定义和管理Spring Bean

    详解如何使用XML配置来定义和管理Spring Bean

    XML 配置文件是 Spring 中传统的 Bean 配置方式,通过定义 XML 元素来描述 Bean 及其依赖关系,在 Spring 框架中,Bean 是由 Spring IoC(控制反转)容器管理的对象,本文将详细介绍如何使用 XML 配置来定义和管理 Spring Bean,需要的朋友可以参考下
    2024-06-06
  • Spring Boot 如何解决富文本上传图片跨域问题

    Spring Boot 如何解决富文本上传图片跨域问题

    这篇文章主要介绍了Spring Boot 如何解决富文本上传图片跨域问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • GateWay路由规则与动态路由详细介绍

    GateWay路由规则与动态路由详细介绍

    这篇文章主要介绍了GateWay路由规则与GateWay动态路由,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • Java泛型机制必要性及原理解析

    Java泛型机制必要性及原理解析

    这篇文章主要介绍了Java泛型机制必要性及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • EasyExcel工具读取Excel空数据行问题的解决办法

    EasyExcel工具读取Excel空数据行问题的解决办法

    EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,下面这篇文章主要给大家介绍了关于EasyExcel工具读取Excel空数据行问题的解决办法,需要的朋友可以参考下
    2022-08-08
  • SpringBoot图文并茂详解如何引入mybatis与连接Mysql数据库

    SpringBoot图文并茂详解如何引入mybatis与连接Mysql数据库

    这篇文章主要介绍了SpringBoot如何引入mybatis与连接Mysql数据库,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • Java实现五子棋单机版

    Java实现五子棋单机版

    这篇文章主要为大家详细介绍了Java实现五子棋单机版,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Java Spring框架简介与Spring IOC详解

    Java Spring框架简介与Spring IOC详解

    Spring 框架是一个轻量级的解决方案,可以一站式地构建企业级应用。它是为了解决 企业应用开发的复杂性而创建的。Spring 使用基本的 JavaBean 来完成以前只可能由 EJB 完成的事情。IOC 是 Inversion of Control 的缩写,多数书籍翻译成控制反转
    2021-09-09
  • JAVA中通过Redis实现延时任务demo实例

    JAVA中通过Redis实现延时任务demo实例

    Redis在2.0版本时引入了发布订阅(pub/sub)功能,在发布订阅中有一个channel(频道),与消息队列中的topic(主题)类似,可以通过redis的发布订阅者模式实现延时任务功能,实例中会议室预约系统,用户预约管理员审核后生效,如未审批,需要自动变超期未处理,使用延时任务
    2024-08-08

最新评论