AOP笔记

AOP概述

AOP:Aspect Oriented Programming,即面向切面编程,其实就是面向特性方法编程。

AOP应用场景

动态代理的主要作用是在运行时创建一个符合特定接口的对象,这个对象可以拦截接口方法的调用,并在调用实际方法前后添加额外的处理逻辑。

Spring AOP

导入依赖:在pom.xml文件中导入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

编写AOP程序:针对于特定方法根据业务需要进行编程

使用@Componentspringboot管理,使用@Aspect注解成为切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
@Aspect
public class TimeAspect {
/**
* ProceedingJoinPoint:表示正在执行的连接点,即正在执行的方法,固定参数
* @Around:环绕通知,在目标方法执行前后添加额外的处理逻辑:第一个*指的是所有类或接口,第二个*指的是所有方法,第三个..指的是所有参数
*/
@Around("execution(* com.nodaoli.service.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object result = pjp.proceed(); // 调用原始方法运行,固定方法
long end = System.currentTimeMillis();
log.info(pjp.getSignature() + "耗时:" + (end - begin));
return result;
}
}

AOP核心概念

  • 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
  • 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
  • 切入点:Pointcut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 目标对象:Target,通知所应用的对象

alt text

通知类型

  1. @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前执行
  3. @After:后置通知,此注解标注的通知方法在目标方法后执行,无论是否有异常都执行
  4. @AfterReturning:返回通知,此注解标注的通知方法在目标方法正常返回后执行,有异常不执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

注意事项:

  • @Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。

同一切入点表达式

定义一个方法,方法名随意,使用@Pointcut注解,指定切入点表达式

1
2
3
4
5
6
7
@Pointcut("execution(* com.nodaoli.service.*.*(..))")
private void pt() {}

@Before("pt()")
public void before() {
// ...
}

AOP 通知顺序

多个切面通知跟过滤器差不多,都是根据类名排序来执行:

  • 字母顺序排名靠前:@Before先执行
  • 字母顺序排名靠前:@After后执行

例子:

1
2
3
4
5
6
@Before1
@Before2
@Before3
@After3
@After2
@After1

使用 @Order(数字) 加在切面类上面来控制顺序

  • @Before:数字小值优先执行
  • @After:数字大值优先执行

切入点表达式

  • 切入点表达式:描述切入点方法的一种表达式
  • 作用:主要用来决定项目中的哪些方法需要加入通知
  • 常见形式:

execution(…)

语法:

1
execution(访问权限? 返回值 包名.类名.?方法名(参数列表) throws 异常列表)
  • 其中带?的表示可以省略的部分
    • 访问修饰符:可省略(比如:public、private、protected、default)
    • 包名.类名:可省略
    • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
  • 可以使用通配符描述切入点
      • :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
    • .. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

@annotation(…)

语法:

1
@annotation(自定义注解类型)

需要自定义一个注解,加上@Retention和@Target两个注解

1
2
3
@Retention(RetentionPolicy.RUNTIME)// 运行时运行
@Target(ElementType.METHOD)// 方法注解
public @interface MyAnnotation {}

然后在方法上面添加自定义注解,就可以匹配到这个方法了。

1
2
@Pointcut("@annotation(com.nodaoli.annotation.MyAnnotation)")
private void pt() {}

连接点

只有@Around环绕通知使用ProceedingJoinPoint,其他通知使用JoinPoint

JoinPoint可以获取连接点信息,而ProceedingJoinPoint除了可以获取连接点信息外,还可以执行原始方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Around("execution(* com.nodaoli.service.*.*(..))"))
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 获取对象类名
pjp.getTarget().getClass().getName();

// 获取方法名
pjp.getSignature().getName();

// 获取运行时传入的数组
Object[] args = pjp.getArgs();
Arrays.toString(args);

// 放行目标方法
pjp.proceed();

// 返回值
return pjp.proceed();
}