SpringAOP

AOP(Aspect Oriented Program,面向切面编程),可以说是面向对象编程的补充,它提供一种“横切”的技术,将影响了多个类的公共行为封装到一个可重用模块,并将其命名为切面(Aspect),AOP 把软件系统分为两个部分:核心关注点和横切关注点,业务处理的主要流程就是核心关注点,而横切关注点就是核心关注点里的一些公共行为,比如权限认证、日志、事物等

术语介绍

  • 切面(Aspect):切入点和通知的结合
  • 连接点(JoinPoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
  • 切入点(Pointcut):对连接点进行拦截的定义
  • 通知(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
  • 目标对象(Target Object):被一个或者多个切面所通知的对象
  • 织入(Weave):将切面应用到目标对象并导致代理对象创建的过程
  • 引入(Introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
通知的类型
名称 说明
before(前置通知) 通知方法在目标方法调用之前执行
after(后置通知) 通知方法在目标方法返回或异常后调用
after-returning(返回后通知) 通知方法会在目标方法返回后调用
after-throwing(抛出异常通知) 通知方法会在目标方法抛出异常后调用
around(环绕通知) 通知方法会将目标方法封装起来

实现原理

Spring AOP 是通过动态代理实现的,通过动态代理,可以对被代理对象的方法进行增强,主要用到了两种动态代理技术:JDK 动态代理 和 CGLIB 库
其中 JDK 动态代理只能对实现了接口的类生成代理,CGLIB 则是针对类实现代理,主要是对指定的类生成一个子类,重写其中的方法

切入点表达式

execute
1
2
3
4
5
6
7
8
9
10
11
12
13
execution(权限修饰符 返回值类型 类路径(方法名) 抛出的异常类型)

// 拦截任意公共方法
execution(public * *(..))

// 拦截以 set 开头的任意方法
execution(* set*(..))

// 拦截 service 包中以 public 修饰的任意方法
execution(public * ink.ckx.test.*.service.*.*(..))

// 拦截 test 包及其子包下的任意方法
execution(* ink.ckx.test..*.*(..))
within
1
2
3
4
5
// 拦截包中任意方法,不包含子包中的方法
within(ink.ckx.service.*)

// 拦截包或者子包中定义的方法
within(ink.ckx.service..*)
this
1
2
// 拦截 AService 所有的子类的所有外部调用方法
this(ink.ckx.Aservice*)
target
1
2
// 拦截 AServiceImpl 下的所有方法
target(ink.ckx.impl.AServiceImpl)
args
1
2
3
4
5
6
7
8
9
10
11
// 拦截任何不带参数的方法
args()

// 拦截带任意参数的方法
args(…)

// 拦截任何只带一个参数,而且这个参数的类型是 String 的方法
args(java.lang.String)

// 拦截带任意个参数,但是第一个参数的类型是 String 的方法
args(java.lang.String,…)
@target
1
2
// 拦截类上有指定注解的所有外部调用方法
@target(ink.ckx.test.Annotation1)
@within
1
2
// 拦截类上具有指定注解的的所有方法
@within(ink.ckx.test.Annotation1)
@annotation
1
2
// 拦截具有指定注解的方法
@annotation(ink.ckx.test.Annotation1)
@args
1
2
3
4
5
6
7
8
// 拦截一个参数的方法,并且这个参数的类型有指定的注解
@args(ink.ckx.test.Annotation1)

// 拦截两个参数的方法,且两个参数的类型上都有指定的注解
@args(ink.ckx.test.Annotation1,ink.ckx.test.Annotation2)

// 拦截多个参数的方法,且第一个参数类型上有指定的注解
@args(ink.ckx.test.Annotation1,..)

使用

依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Slf4j
@Component
@Aspect
public class TestAspect {

@Pointcut("execution(public * ink.ckx.test.*.service.*.*(..))")
public void aspect() {
}

@Before("aspect()")
public void before(JoinPoint joinPoint) {
log.info("before:", joinPoint);
}

@After("aspect()")
public void after(JoinPoint joinPoint) {
log.info("after:", joinPoint);
}

@Around("aspect()")
public Object around(ProceedingJoinPoint joinPoint) {
long start = System.currentTimeMillis();
Object result;
try {
result = joinPoint.proceed();
long end = System.currentTimeMillis();
log.info("around:{},Use time:{} ms", joinPoint, (end - start));
return result;
} catch (Throwable e) {
long end = System.currentTimeMillis();
log.info("around:{},Use time:{} ms,with exception:{}", joinPoint, (end - start), e.getMessage());
}
return null;
}

@AfterReturning(pointcut = "aspect()", returning = "retVal")
public void afterReturn(Object retVal) {
log.info("afterReturn:{}", retVal);
}

@AfterThrowing(pointcut = "aspect()", throwing = "ex")
public void afterThrow(JoinPoint joinPoint, Exception ex) {
log.info("afterThrow:{},with exception:{}", joinPoint, ex.getMessage());
}
}