在SpringBoot中实践AOP编程

具体实践

Spring AOP是Spring框架中一个支持实现面向切面编程的模块,由于Spring Boot已经把Spring框架组合得非常好用,所以在基于Spring Boot框架的项目中实现AOP编程也是非常方便,具体来说可以分为如下几步:
第一步: 在项目中引入依赖配置。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

第二步: 定义切面,切入点和通知,三者可以直接在一个类中完成。
使用注解@Aspect来定义切面(注:需要在应用注解@Aspect的类上使用注解@Component标识这是一个容器管理的Bean),使用注解@Pointcut来定义切入点,使用注解@Before@After@Around@AfterReturning@AfterThrowing来定义不同的通知。

@Aspect // 使用注解@Aspect定义切面
@Component // 同时使用注解@Component标识这是一个IoC容器管理的Bean
public class AspectSample {
     // 定义切点
     // 使用@Pointcut注解定义切点
     // 切点的表达式有多种,如:execution,within,@annotation,bean()
     // 详见:https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html#aop-pointcuts-combining

    // 匹配类org.chench.springboot.scaffolding.controller.AOPController的所有方法
    @Pointcut("execution(* org.chench.springboot.scaffolding.controller.AOPController.*(..))")
    public void pointcutSample() {}


    // 定义通知
    // 可以用于定义通知的注解有5个:
    // 1. @Before(前置通知:目标方法执行前执行)
    // 2. @After(后知通知:无论目标方法正常返回还是抛出异常都执行)
    // 3. @Around(环绕通知:目标方法执行前和返回后执行)
    // 4. @AfterReturning(返回通知:目标方法正常返回后执行)
    // 5. @AfterThrowing(异常通知:目标方法抛出异常后执行)

    // 前置通知:目标方法执行前执行
    @Before("pointcutSample()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(String.format("这里是前置通知执行:%s", new Date()));
    }

    // 后置通知:无论目标方法正常返回还是抛出异常都执行
    @After("pointcutSample()")
    public void afterAdvice(JoinPoint joinPoint) {
        System.out.println(String.format("这里是后置通知执行:%s", new Date()));
    }

    // 环绕通知执行:目标方法执行前和返回后执行
    // 注意:
    // 1.在环绕通知中一定要返回目标方法的返回值,否则客户端就无法接收到结果啦
    // 2.如果在环绕通知中捕获了目标方法执行时抛出的异常,则异常通知对应的切面逻辑将得不到执行
    @Around("pointcutSample()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println(String.format("这里是环绕通知执行前:%s", new Date()));
        Object result = proceedingJoinPoint.proceed();
        System.out.println(String.format("这里是环绕通知执行后:%s", new Date()));
        return result;
    }

    // 返回通知:目标方法正常返回后执行
    @AfterReturning(value = "pointcutSample()", returning = "r")
    public void afterRunningAdvice(JoinPoint joinPoint, Object r) {
        System.out.println(String.format("这里是返回通知执行:%s %s", new Date(), r));
    }

    // 异常通知:目标方法抛出异常后执行
    // 有意思的是:
    // 如果在项目中同时使用了@ControllerAdvice注解进行全局异常处理,同时也定义了异常通知
    // 那么异常通知将在@ControllerAdvice之前得到执行
    @AfterThrowing(value = "pointcutSample()", throwing = "e")
    public void afterThrowingAdvice(JoinPoint joinPoint, Throwable e) {
        System.out.println(String.format("这里是异常通知执行:%s", new Date()));
    }
}

写在最后

关于Spring对AOP的支持有如下几点总结:
1.切入点只支持方法
2.不同类型的通知执行的先后顺序不同,假设对于一个相同的切入点定义了所有通知类型。

  • 当目标方法没有抛出异常时,各个通知的执行顺序如下:
这里是aroundAdvice中目标方法执行前:Tue Jul 18 00:20:31 CST 2023
这里是beforeAdvice执行:Tue Jul 18 00:20:31 CST 2023
执行目标方法m1:Tue Jul 18 00:20:31 CST 2023
这里是aroundAdvice中目标方法执行后:Tue Jul 18 00:20:31 CST 2023
这里是afterAdvice执行:Tue Jul 18 00:20:31 CST 2023
这里是afterRunningAdvice执行:Tue Jul 18 00:20:31 CST 2023
  • 当目标方法执行时抛出异常,且在环绕通知中没有明确捕获该异常,则各个通知的执行顺序如下:
这里是aroundAdvice中目标方法执行前:Tue Jul 18 00:22:54 CST 2023
这里是beforeAdvice执行:Tue Jul 18 00:22:54 CST 2023
执行目标方法m2:Tue Jul 18 00:22:54 CST 2023
这里是afterAdvice执行:Tue Jul 18 00:22:54 CST 2023
这里是afterThrowingAdvice执行:Tue Jul 18 00:22:54 CST 2023

显然,此时就缺少了这里是aroundAdvice中目标方法执行后这里是afterRunningAdvice执行,但是这里是afterThrowingAdvice执行却出现了。

3.如果使用了@ControllerAdvice实现全局异常拦截,同时也定义了异常通知@AfterThrowing,那么异常通知@AfterThrowing将在@ControllerAdvice之前得到执行。

@ControllerAdvice
public class GlobalExceptionAdvice {
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Object handleException(HttpServletRequest req, HttpServletResponse resp, Exception ex) {
        System.out.println(String.format("这里是@ControllerAdvice:%s", new Date()));
        return JsonVO.build().setCode(-1).setMessage("error");
    }
}

当目标方法抛出异常后,则输出:

这里是aroundAdvice中目标方法执行前:Tue Jul 18 00:28:48 CST 2023
这里是beforeAdvice执行:Tue Jul 18 00:28:48 CST 2023
执行目标方法m2:Tue Jul 18 00:28:48 CST 2023
这里是afterAdvice执行:Tue Jul 18 00:28:48 CST 2023
这里是afterThrowingAdvice执行:Tue Jul 18 00:28:48 CST 2023 # @AfterThrowing将在@ControllerAdvice之前得到执行
这里是@ControllerAdvice:Tue Jul 18 00:28:48 CST 2023

4.每种通知方法中都可以通过第一个参数获取JoinPoint对象(环绕通知的方法参数ProceedingJoinPoint也是JoinPoint类型),并且在@AfterReturning通知中还可以获取目标方法的返回值,在@AfterThrowing通知中可以获取目标方法抛出的异常。
5.拦截器HandlerInterceptor也是Spring AOP的一个具体应用,当同时存在拦截器HandlerInterceptor和通知Advice时,拦截器会在通知之前执行,因为拦截器处理的是uri,而通知处理的是方法,一个在前,一个在后。Spring提供的拦截器机制跟Servlet中的Filter机制很像,但是他们属于不同范畴(后者属于Servlet规范中的内容,而前者属于Spring框架提供的支持)。

【参考】
aop-pointcuts-combining
Spring AOP - 注解方式使用介绍
原来这才是Spring Boot使用AOP的正确姿势
在SpringBoot中使用AOP——通知中的参数
AOP通知获取数据(参数、返回值、异常)
Spring AOP使用:自定义注解、通知(简单使用和原理了解)
SpringBoot之Filter注册
优雅的使用SpringBoot 中的Filter
Spring Boot拦截器(Interceptor)详解
Spring 过滤器 拦截器 AOP区别

posted @ 2023-07-18 00:50  nuccch  阅读(155)  评论(0编辑  收藏  举报