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()); } }
|