什么是AOP
AOP(Aspect Oriented Programming),面向切面编程,是一种编程范式,是OOP(Object Oriented Programming)的补充。OOP是面向对象编程,AOP是面向切面编程。
AOP的核心思想是将程序的业务逻辑和系统级服务分开,通过切面将系统级服务应用到业务逻辑中,从而实现业务逻辑和系统级服务的解耦。
AOP的核心概念
- 连接点(JoinPoint):可以被AOP控制的方法
- 通知(Advice):指哪些重复的逻辑,也就是共性功能
- 切入点(PointCut):匹配连接点的条件,通知仅会在切入点方法被执行时应用
- 切面(Aspect):通知和切入点的组合
- 目标对象(Target):被代理的对象
AOP的实现
动态代理是AOP最主流的实现方式,Spring AOP就是基于动态代理实现的。在管理bean对象的时候,Spring会为bean对象创建一个代理对象,当调用代理对象的方法时,会先调用代理对象的增强方法,再调用目标对象的方法。
AOP的应用场景
- 日志记录
- 权限控制
- 事务管理
…
例如,进行记录方法运行时间时,可以通过AOP实现,而不需要在每个方法中都写一段记录时间的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Slf4j @Aspect @Component public class TimeAspect {
@Around("execution(* com.example.demo.service.*.*(..))") public Object time(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
log.info("方法{}运行时间为{}ms", joinPoint.getSignature().getName(), end - start); return result; } }
|
在上面的代码中,通过@Aspect注解声明一个切面,通过@Around注解声明一个环绕通知,通过execution表达式指定切入点,即需要增强的方法,通过ProceedingJoinPoint对象获取方法的信息,通过proceed方法调用目标方法,最后记录方法的运行时间。
AOP的优点
- 代码无侵入性,不需要修改原有代码
- 代码复用性,将公共代码抽取出来,可以在多个方法中复用
- 代码解耦,将系统级服务和业务逻辑分开,提高代码的可维护性
深入学习
通知类型
- 前置通知(Before):在目标方法执行前执行
- 后置通知(After):在目标方法执行后执行,无论目标方法是否抛出异常
- 环绕通知(Around):在目标方法执行前后执行
- 异常通知(AfterThrowing):在目标方法抛出异常后执行
- 返回通知(AfterReturning):在目标方法返回结果时执行,有异常时不执行
注意,@Around通知需要调用proceed方法,才能执行目标方法,否则目标方法不会执行。同时,proceed的返回值应该为Object类型,否则会报错。
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
| @Slf4j @Aspect @Component public class MyAspect {
@Pointcut("execution(* com.example.demo.service.*.*(..))") public void pointCut() {}
@Before("pointCut()") public void before(JoinPoint joinPoint) { log.info("before"); }
@After("pointCut()") public void after(JoinPoint joinPoint) { log.info("after"); }
@Around("pointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { log.info("around before"); Object result = joinPoint.proceed(); log.info("around after"); return result; }
@AfterThrowing("pointCut()") public void afterThrowing(JoinPoint joinPoint) { log.info("afterThrowing"); }
@AfterReturning("pointCut()") public void afterReturning(JoinPoint joinPoint) { log.info("afterReturning"); } }
|
通知顺序
当有多个切面的切入点都匹配到了目标方法时,多个切面都会执行
执行顺序
- 不同切面类中,默认按照切面类的类名的字典顺序执行
- 目标方法前:字母靠前的切面类先执行
- 目标方法后:字母靠后的切面类先执行
- 用@Order注解指定切面的执行顺序
- 目标方法前:数字小的切面类先执行
- 目标方法后:数字大的切面类先执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Slf4j @Aspect @Component @Order(1) public class MyAspect1 {
@Pointcut("execution(* com.example.demo.service.*.*(..))") public void pointCut() {}
@Before("pointCut()") public void before(JoinPoint joinPoint) { log.info("before1"); }
@After("pointCut()") public void after(JoinPoint joinPoint) { log.info("after1"); } }
|
切入点表达式
两种常用的切入点表达式
execution表达式
execution表达式用于匹配方法的执行,语法为execution(访问修饰符? 返回值 包名.类名.?方法名(参数类型) throws 异常类型?)
可以使用通配符描述切入点
*
:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
execution(* com.*.service*(*))
:匹配com包下的所有service开头的方法,且只有一个参数
..
:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
execution(* com.example..DeptService.*(..))
:匹配com.example包及其子包下的DeptService类的所有方法
annotation表达式
annotation表达式用于匹配带有指定注解的方法,语法为@annotation(注解类型)
自定义注解
1 2 3 4
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { }
|
使用自定义注解
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Slf4j @Aspect @Component public class MyAspect {
@Pointcut("@annotation(com.example.demo.annotation.MyAnnotation)") public void pointCut() {}
@Before("pointCut()") public void before(JoinPoint joinPoint) { log.info("before"); } }
|
这样,只有带有@MyAnnotation注解的方法才会被增强
连接点
在spring中用JoinPoint抽象了连接点,可以通过JoinPoint获取方法的信息
对于@Around通知,可以通过ProceedingJoinPoint调用proceed方法执行目标方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Slf4j @Aspect @Component public class MyAspect {
@Pointcut("execution(* com.example.demo.service.*.*(..))") public void pointCut() {}
@Around("pointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { log.info("around before"); Object result = joinPoint.proceed(); log.info("around after"); return result; } }
|
对于其他通知,可以通过JoinPoint获取方法的信息,JointPoint是ProceedingJoinPoint的父类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Slf4j @Aspect @Component public class MyAspect {
@Pointcut("execution(* com.example.demo.service.*.*(..))") public void pointCut() {}
@Before("pointCut()") public void before(JoinPoint joinPoint) { log.info("before"); log.info("方法名:{}", joinPoint.getSignature().getName()); log.info("参数:{}", joinPoint.getArgs()); } }
|
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
| @Slf4j @Aspect @Component public class MyAspect {
@Pointcut("execution(* com.example.demo.service.*.*(..))") public void pointCut() {}
@Around("pointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { String className = joinPoint.getTarget().getClass().getName(); log.info("类名:{}", className);
String methodName = joinPoint.getSignature().getName(); log.info("方法名:{}", methodName);
Object[] args = joinPoint.getArgs(); log.info("参数:{}", args);
Object result = joinPoint.proceed(); log.info("返回值:{}", result);
return result; } }
|