SpringAOP

AOP: Aspect Oriented Programming(面向切面编程、面向方面编程),可简单理解为面向特定方法编程。可以实现记录操作日志、事务管理、权限控制等操作。

步骤:

  • 引入AOP依赖

  • 编写AOP的程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Aspect
    @Component
    public class DemoAspect {
    @Before("execution(* top.eternity0126.service.impl.*.*(..))")
    public void before(){
    ...
    }

    @Around("execution(* top.eternity0126.service.impl.*.*(..))")
    public Object around(ProceedingJointPoint pjp) {
    ...
    Object result = pjp.proceed();
    ...
    return result;
    }
    }

核心概念:

  • 连接点:JoinPoint,可以被AOP控制的方法
  • 通知:Advice,指重复的逻辑,也就是共性功能,最终体现为一个方法
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)

  • 目标对象:Target,通知所应用的对象

实现原理:AOP使用了动态代理技术

通知类型

根据通知方法执行时机不同,分为:

  • @Around:环绕通知,通知方法在目标方法运行前、后都被执行
  • @Before:前置通知,通知方法在目标方法运行前被执行
  • @After:后置通知,通知方法在目标方法运行后被执行,无论是否有异常都会执行
  • @AfterReturning:返回后通知,通知方法在目标方法运行后被执行,有异常不会执行
  • @AfterThrowing:异常后通知,通知方法在目标方法报异常后被执行

@Around需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要。

@Around的返回值必须指定为Object以接收原始方法的返回值。

@Pointcut注解可以将公共的切点表达式抽取出来:

1
2
3
4
5
6
// 声明切入点
@Pointcut("execution(* top.eternity0126.service.impl.*.*(..))")
private void pt(){} // private仅能在当前切面类中引用,public则在外部切面类中也能引用

// 引用切入点
@Around("pt()")

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。

执行顺序:不同切面类中,默认按照切面类的类名字母排序:

  • 目标方法前的通知方法:字母排名靠前的先执行
  • 目标方法后的通知方法:字母排名靠前的后执行

可以通过在切面类上加上注解@Order(num)控制执行顺序:

  • 目标方法前的通知方法:数字小的先执行
  • 目标方法后的通知方法:数字小的后执行

切入点表达式

描述切入点方法通常基于接口描述,增强拓展性。

常见形式:

  • execution(...):根据方法的签名来匹配,支持逻辑与或

    1
    2
    3
    execution(访问修饰符 返回值 包名.类名.方法名(方法参数) throws 异常)

    execution(void top.eternity0126.service.impl.DeptServiceImpl.delete(java.lang.Integer))

    其中可省略的部分:

    • 访问修饰符,如public、protected
    • 包名.类名(不建议)
    • throws 异常(对应声明上抛出的异常)

    特殊符号:

    • *:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
    • ..:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
  • annotation(...):根据注解匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 定义
    @Target(ElementType.METHOD) // 注解用于方法
    @Retention(RetentionPolicy..RUNTIME) // 注解运行时生效
    public @interface LogOperation {}

    @Before("@annotation(top.eternity0126.anno.LogOperation)")
    public void before(){
    ...
    }

    // 使用
    @LogOperation
    ...

连接点

Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

对于@Around注解,只能使用ProceedingJoinPoint,对于其他注解只能使用JoinPointProceedingJoinPoint的父类)。

  • 获取目标对象:

    1
    Object target = joinPoint.getTarget();
  • 获取目标类:

    1
    String className = joinPoint.getTarget().getClass().getName();
  • 获取目标方法:

    1
    String methodName = joinPoint.getSignature().getName();
  • 获取目标方法参数:

    1
    Object[] args = joinPoint.getArgs();