一、AOP的定义
AOP,也就是 Aspect - oriented Programming,译为面向切面编程,是计算机科学中的一个设计思想,旨在通过切面技术为业务主体增加额外的通知(Advice),从而对声明为“切点”(Pointcut)的代码块进行统一管理和装饰。
注:一般在项目的日志模块中使用。
AOP 是对面向对象编程( Object - oriented Programming,简称 OOP)的一种补充,OOP 的核心单元是类( class),而 AOP 的核心单元是切面( Aspect)。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而降低耦合度,提高程序的可重用性,同时也提高了开发效率。
注: AOP 理解为贯穿于方法之中,在方法执行前、执行时、执行后、返回值后、异常后要执行的操作。
二、AOP的相关术语
AOP 涉及到的 5 个关键术语:
1. 横切关注点,从每个方法中抽取出来的同一类非核心业务。
2. 切面(Aspect),对横切关注点进行封装的类,每个关注点体现为一个通知方法;通常使用 @Aspect 注解来定义切面。
3. 通知(Advice),切面必须要完成的各个具体工作,比如我们要获得日志调用接口前后的时长,就需要在调用接口前后记录时间,再取差值。
通知的方式有五种:
- @Before:通知方法会在目标方法调用之前执行
- @After:通知方法会在目标方法调用后执行
- @AfterReturning:通知方法会在目标方法返回后执行
- @AfterThrowing::通知方法会在目标方法抛出异常后执行事
- @Around:把整个目标方法包裹起来,在被调用前和调用之后分别执行通知方法
例如:
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Before("serviceMethods()")
public void beforeServiceMethods(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void afterReturningServiceMethods(JoinPoint joinPoint, Object result) {
System.out.println("After returning method: " + joinPoint.getSignature() + ", with result: " + result);
}
}
4. 连接点(JoinPoint),通知应用的时机,比如接口方法被调用时就是日志切面的连接点。
5. 切点(Pointcut),通知功能被应用的范围,比如本篇日志切面的应用范围是所有 controller 的接口。通常使用 @Pointcut 注解来定义切点表达式。
三、切点表达式的基本结构
@Pointcut("切点表达式")
public void pointcutMethod() {}
1、切点表达式语法
切点表达式由多个部分组成,每个部分用于匹配不同的连接点。
主要有以下几种常用的表达式类型:
1.1、'execution
' 表达式
用于匹配方法执行的连接点。语法如下:
execution(modifiers-pattern?
ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern)
throws-pattern?)
- '
modifiers-pattern
':方法的修饰符,如public
,protected
等。可以省略。 - '
ret-type-pattern
':返回类型模式,可以是具体类型,也可以是通配符*
表示所有类型。 - '
declaring-type-pattern
':方法所属类的模式,可以是具体类名,也可以是包名加通配符。 - '
name-pattern
':方法名模式,可以是具体方法名,也可以是通配符。 - '
param-pattern
':参数模式,用于匹配方法的参数列表。 - '
throws-pattern
':抛出异常模式,可以省略。
例如:
@Pointcut("execution(public * com.example.service.*.*(..))")
public void anyPublicMethod() {}
匹配 'com.example.service
' 包中所有类的任何 public
方法。
1.2、'within
' 表达式
用于匹配指定类型内的所有连接点。语法如下:
within(type-pattern)
例如:
@Pointcut("within(com.example.service..*)")
public void inServiceLayer() {}
匹配 'com.example.service
' 包及其子包中的所有类的所有方法。
1.3、'this
' 表达式
用于匹配代理对象是指定类型的连接点。语法如下:
this(type)
例如:
@Pointcut("this(com.example.service.MyService)")
public void proxyIsMyService() {}
匹配代理对象是 'MyService
' 类型的连接点。
1.4、'target
' 表达式
用于匹配目标对象(即被代理的对象)是指定类型的连接点。语法如下:
target(type)
例如:
@Pointcut("target(com.example.service.MyService)")
public void targetIsMyService() {}
匹配目标对象是 'MyService
' 类型的连接点。
1.5、'args
' 表达式
用于匹配方法参数是指定类型的连接点。语法如下:
args(argument-type-pattern)
例如:
@Pointcut("args(java.io.Serializable)")
public void argsSerializable() {}
匹配方法参数是 'Serializable' 类型的连接点。
1.6、'@annotation
' 表达式
用于匹配方法上有指定注解的连接点。语法如下:
@annotation(annotation-type)
例如:
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
匹配所有带有 '@Transactional
' 注解的方法。
2、组合表达式
切点表达式可以通过逻辑运算符进行组合。
组合类型:
- '
&&
' :与 - '
||
' :或 - '
!
' :非
例如:
@Pointcut("execution(* com.example..*.*(..)) && !within(com.example.service..*)")
public void excludeServiceLayer() {}
匹配 'com.example
' 包及其子包中的所有方法,但不包括 'com.example.service
' 包及其子包中的方法。
四、小结
遵循Spring AOP中切点表达式的基本规范和使用方法,通过合理定义切点表达式,可以精确控制切面逻辑的应用范围。