6. Spring - AOP - 基本使用

系列篇幅

前言

本文来整理一下AOP相关知识,这里不直接介绍理论了,我们看下日常工作中如何使用

Filter、Interceptor、ControllerAdvice、AOP的关系

6. Spring - AOP - 基本使用

流程说明

  1. 加入依赖 spring-aspects
  2. 将业务逻辑组件和切面类都加入到容器中,并将切面类加入注解 @Aspect
  3. 在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行 切入点表达式
  4. 开启基于注解的aop模式 @EnableAspectJAutoProxy
通知方法 说明
前置通知(@Before) 在目标方法运行之前运行
后置通知(@After) 在目标方法正常结束/异常结束之后运行
返回通知(@AfterReturning) 在目标方法正常返回之后运行
异常通知(@AfterThrowing) 在目标方法出现异常之后运行
环绕通知(@Around) 动态代理,手动推进目标方法运行 joinPoint.procced()

引入依赖

lombok 推荐: [Lombok 的使用](…/…/Java/编程技巧/Lombok 的使用.md)

<!-- lombok 个人习惯非必须 -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>
<!-- aop切面必须导入 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>

常用方法说明

JoinPoint参数必须放在第一位

在某个类方法执行前调用

@After("execution(public int com.xm.demo.MathCalculator.*(..))")
public void logEnd(JoinPoint joinPoint){
  String methodName = joinPoint.getSignature().getName();
  System.out.printf("方法:%s 结束,切面类:logEnd 方法", methodName);
}

抽离切面公共表达式,方便阅读与封装

@Pointcut("execution(public int com.xm.demo.MathCalculator.*(..))")
public void pointCut() {
}

/**
 * 本类的公共切入表达式
 */
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
  Object[] args = joinPoint.getArgs();
  String methodName = joinPoint.getSignature().getName();
  System.out.printf("方法:%s 即将运行,参数列表:%s,切面类:logStart 方法", 
                    methodName, Arrays.asList(args));
}

/**
 * 其他类的公共切入表达式
 */
@After("com.xm.demo.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint) {
  String methodName = joinPoint.getSignature().getName();
  System.out.printf("方法:%s 结束,切面类:logEnd 方法", methodName);
}

完整例子

定义功能类,等待切面日志

public class MathCalculator {
    public int div(int i, int j) {
        System.out.printf("方法:MathCalculator.div(%d,%d)\n", i, j);
        return i / j;
    }
}

定义切面类@Aspect 一定要加,否则无效

@Aspect
public class LogAspects {

  /**
   * 抽取公共的切入点表达式
   */
  @Pointcut("execution(public int com.xm.demo.MathCalculator.*(..))")
  public void pointCut() {
  }


  @Before("pointCut()")
  public void logStart(JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();
    String methodName = joinPoint.getSignature().getName();
    System.out.printf("方法:%s 即将运行,参数列表:%s,切面类:logStart 方法\n",
                      methodName, Arrays.asList(args));
  }

  @After("com.xm.demo.LogAspects.pointCut()")
  public void logEnd(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.printf("方法:%s 结束,切面类:logEnd 方法\n", methodName);
  }

  @AfterReturning(value = "pointCut()", returning = "result")
  public void logReturn(JoinPoint joinPoint, Object result) {
    String methodName = joinPoint.getSignature().getName();
    System.out.printf("方法:%s 正常返回,返回结果:%s,切面类:logReturn方法\n",
                      methodName, result);
  }

  @AfterThrowing(value = "pointCut()", throwing = "exception")
  public void logException(JoinPoint joinPoint, Exception exception) {
    String methodName = joinPoint.getSignature().getName();
    System.out.printf("方法:%s 异常,exception:%s,切面类:logException方法\n",
                      methodName, exception);
  }

}

配置类 @EnableAspectJAutoProxy 一定要加,否则无效

@EnableAspectJAutoProxy
@Configuration
public class MainConfig {

    /**
     * 业务逻辑类加入容器中
     */
    @Bean
    public MathCalculator mathCalculator(){
        return new MathCalculator();
    }

    /**
     * 切面类加入到容器中
     */
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }

}

使用

public class MainTest {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
      new AnnotationConfigApplicationContext(MainConfig.class);
    // 不实用ioc容器的话,切面是无效的
    MathCalculator calculator = context.getBean(MathCalculator.class);
    int div = calculator.div(4, 2);
    System.out.printf("result:%d", div);
  }
}

正常结果

方法:div 即将运行,参数列表:[4, 2],切面类:logStart 方法
方法:MathCalculator.div(4,2)
方法:div 结束,切面类:logEnd 方法
方法:div 正常返回,返回结果:2,切面类:logReturn方法
result:2

失败结果

方法:div 即将运行,参数列表:[1, 0],切面类:logStart 方法
方法:MathCalculator.div(1,0)
方法:div 结束,切面类:logEnd 方法
方法:div 异常,exception:java.lang.ArithmeticException: / by zero,切面类:logException方法
6. Spring - AOP - 基本使用6. Spring - AOP - 基本使用 全栈-民 发布了182 篇原创文章 · 获赞 33 · 访问量 25万+ 私信 关注
上一篇:SpringAop 2.x基于命名空间的配置实现 前置,后置,环绕,异常通知的小案例及解析


下一篇:12个Android中使用AspectJ实现AOP的实例操作