AOP 即面向切面编程(Aspect Oriented Program),可以说是面向对象编程的补充,它提供一种“横切”的技术,将影响了多个类的公共行为封装到一个可重用模块,并将其命名为切面(Aspect),AOP 把软件系统分为两个部分:核心关注点和横切关注点,业务处理的主要流程就是核心关注点,而横切关注点就是核心关注点里的一些公共行为,比如权限认证、日志、事物等
1. 术语介绍
- 切面(Aspect):切入点和通知的结合
- 连接点(Joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
- 切入点(Pointcut):对连接点进行拦截的定义
- 通知(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
- 目标对象(Target Object):被一个或者多个切面所通知的对象
- 织入(Weave):将切面应用到目标对象并导致代理对象创建的过程
- 引入(Introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
1.1 通知的类型
名称 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
2. 实现原理
Spring AOP 是通过动态代理实现的,通过动态代理,可以对被代理对象的方法进行增强
主要用到了两种动态代理技术:JDK 动态代理 和 CGLIB 库
其中 JDK 动态代理只能对实现了接口的类生成代理,CGLIB 则是针对类实现代理,主要是对指定的类生成一个子类,重写其中的方法
3. 切入点表达式
3.1 execute
execution(权限修饰符 返回值类型 类路径(方法名) 抛出的异常类型)
// 拦截任意公共方法
execution(public * *(..))
// 拦截以 set 开头的任意方法
execution(* set*(..))
// 拦截 service 包中以 public 修饰的任意方法
execution(public * ink.ckx.test.*.service.*.*(..))
// 拦截 test 包及其子包下的任意方法
execution(* ink.ckx.test..*.*(..))
3.2 within
// 拦截包中任意方法,不包含子包中的方法
within(ink.ckx.service.*)
// 拦截包或者子包中定义的方法
within(ink.ckx.service..*)
3.3 this
// 拦截 AService 所有的子类的所有外部调用方法
this(ink.ckx.Aservice*)
3.4 target
// 拦截 AServiceImpl 下的所有方法
target(ink.ckx.impl.AServiceImpl)
3.5 args
// 拦截任何不带参数的方法
args()
// 拦截带任意参数的方法
args(…)
// 拦截任何只带一个参数,而且这个参数的类型是 String 的方法
args(java.lang.String)
// 拦截带任意个参数,但是第一个参数的类型是 String 的方法
args(java.lang.String,…)
3.6 @target
// 拦截类上有指定注解的所有外部调用方法
@target(ink.ckx.test.Annotation1)
3.7 @within
// 拦截类上具有指定注解的的所有方法
@within(ink.ckx.test.Annotation1)
3.8 @annotation
// 拦截具有指定注解的方法
@annotation(ink.ckx.test.Annotation1)
3.9 @args
// 拦截一个参数的方法,并且这个参数的类型有指定的注解
@args(ink.ckx.test.Annotation1)
// 拦截两个参数的方法,且两个参数的类型上都有指定的注解
@args(ink.ckx.test.Annotation1,ink.ckx.test.Annotation2)
// 拦截多个参数的方法,且第一个参数类型上有指定的注解
@args(ink.ckx.test.Annotation1,..)
4. 使用
4.1 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.2 示例
@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 void around(JoinPoint joinPoint) {
long start = System.currentTimeMillis();
try {
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
log.info("around:{},Use time:{} ms", joinPoint, (end - start));
} catch (Throwable e) {
long end = System.currentTimeMillis();
log.info("around:{},Use time:{} ms,with exception:{}", joinPoint, (end - start), e.getMessage());
}
}
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint) {
log.info("afterReturn:{}", joinPoint);
}
@AfterThrowing(pointcut = "aspect()", throwing = "ex")
public void afterThrow(JoinPoint joinPoint, Exception ex) {
log.info("afterThrow:{},with exception:{}", joinPoint, ex.getMessage());
}
}
Q.E.D.