一、前言
- 什么是AOP。
- Spring处理AOP时机什么样。
- 如何生成代理对象。
- 调用代理对象的方法时面对多重增强逻辑如何处理,优先级是什么。
- AOP有哪些应用
二、体系认知
2.1 总体结构思维导图
2.2 流程图
2.3 基本通知(Advice)类图
实现了MethodInterceptor接口的类才能被调用。因此需要通过适配器模式将AspectJMethodBeforeAdvice和AspectJAfterReturningAdvice包装起来。
五种通知注解逻辑都是在下面几个实体类中实现的。
2.4 通知链的处理过程
可以将一个通知增强逻辑+被代理方法当成一个代理方法,每一层都对下一层的代理方法进行增强。
2.4.1 正常执行的顺序
由图可知,正常执行的顺序为:
- @Around的前置逻辑->下一层代理方法
- @Before的增强逻辑
- @AfterReturning的增强逻辑
- @After的增强逻辑
- @Around的后置逻辑
2.4.2 出现异常的执行顺序
根据上图可知,@AfterThrowing的Try之后的流程(真实被代理方法)发生了异常,会被Catch到,然后触发@AfterThrowing的增强逻辑。
- @Around的前置逻辑->下一层代理方法
- @Before的增强逻辑
- @AfterThrowing的增强逻辑
- @After的增强逻辑
- 无法执行@Around的后置逻辑,除非在@Around的增强方法中对下一层代理方法进行了try catch的处理。
2.4.3 关于发生异常时@After增强逻辑的触发时机
由图可知,
在@After的Try之后的流程(@AfterReturning增强逻辑、@AfterThrowing增强逻辑、真实代理方法)发生了异常,都能触发@After的增强逻辑,因为@After的增强逻辑写在了finally里。
在@After的Try之前的流程发生了异常,无法触发。
2.4.4 关于通知链对于异常的处理
即使@After将增强逻辑写在finally中,@AfterThrowing将异常catch了。
但是
- @After用了两层try,内层try没catch异常,外层catch到异常后还会向上抛出异常。
- @AfterThrowing将异常catch,调用完增强逻辑,再次向上抛出异常。
三、基本概念
3.1 AOP的术语
术语 | 描述 |
---|---|
Aspect(切面) | 一个模块具有一组提供横切需求的 APIs,简单来说就是封装用于横向插入系统功能的类。 |
Join point(连接点) | 在程序执行过程中的某个阶段点,你也能说,它是在实际的应用程序中,其中一个操作将使用 Spring AOP 框架。 |
Advice(通知/增强处理) | AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现。 |
Pointcut(切入点) | 切面与程序的交叉点,即那些需要被处理的连接点。即定义了这个Adivce 应用到哪些 Joinpoint 上。 |
Introduction(引入) | 引用允许你添加新方法或属性到现有的类中。也就是把切面(新方法属性:通知定义的)用到目标类中。 |
Target object(目标对象) | 指所有被通知的对象,也称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象。 |
Proxy(代理) | 增强后的代理类。 |
Weaving(织入) | 将 Advice 应用到 Target Object 上的整个过程叫织入。 |
3.1.1 注解
注解 | 功能 | 通知代理优先级(越小越先处理) | 处理逻辑的类 | 描述 |
---|---|---|---|---|
@Pointcut | 决定什么类或方法会被增强 | 注解为了避免相同的匹配规则被定义多处,专门定义该方法设置执行的匹配规则,各个自行调用即可。 | ||
@Around | 方法执行前后进行增强 | 1 | AspectJAroundAdvice | 环绕通知 |
@Before | 方法执行前执行增强逻辑。 | 2 | MethodBeforeAdviceInterceptor. AspectJMethodBeforeAdvice | 前置通知。因AspectJMethodBeforeAdvice未实现MethodInterceptor接口,所以通过适配器转换 |
@After | 在方法执行结束后执行。获取不到返回值。 被代理方法及低于After优先级通知(AfterReturning/AfterThrowing)抛出异常,都可以执行After的方法。 高于After优先级的通知抛出异常,则After的增强逻辑无法执行。 | 3 | AspectJAfterAdvice | 最终通知 |
@AfterReturning | 在方法执行结束后执行。增强逻辑中可获取返回值。若方法抛出异常则无法执行。 | 4 | AfterReturningAdviceInterceptor AspectJAfterReturningAdvice | 后置通知。因AspectJAfterReturningAdvice未实现MethodInterceptor接口,所以通过适配器转换 |
@AfterThrowing | 被代理方法抛出异常时会执行。 | 5 | AspectJAfterThrowingAdvice | 异常通知 |
@Aspect | 管理增强逻辑 | 标注当前类为一个切面类 |
3.1.2 @Pointcut表达式
3.2 通知优先级
在调用代理对象的方法时,会根据通知链递归调用。而通知链的默认顺序是[Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class],通知相同按照方法名称排序。
四、使用样例
POM依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
日志记录
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLog {
/**
* 日志输出内容
*
* @return 输出内容
*/
String value() default "";
/**
* 是否输出参数
*
* @return 是否输出参数
*/
boolean showParams() default true;
}
@Slf4j
@Aspect
@Component
public class ApiLogAspect {
/**
* 绑定方法上的注解;结合swagger 注解 记录api日志;
* @param joinPoint 切点
* @param apiLog 消息注解
* @return 返回值
* @throws Throwable 抛出异常
* @see ApiLogAspect#printLogForMethod
*/
@Around("@annotation(apiLog)")
public Object printLogForMethod(ProceedingJoinPoint joinPoint, ApiLog apiLog) throws Throwable {
// 查询方法名称
String methodDescription = findMethodDescription(joinPoint, apiLog);
// 拼接消息并输出
log.info(appendMessage(joinPoint, apiLog, methodDescription));
// 方法计时
return calcRunningTime(joinPoint, methodDescription);
}
private Object calcRunningTime(ProceedingJoinPoint joinPoint, String methodDescription) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
log.info(methodDescription + "方法执行时间为:{}ms", stopWatch.getTotalTimeMillis());
return result;
}
private String findMethodDescription(ProceedingJoinPoint joinPoint, ApiLog apiLog) {
String methodDescription = apiLog.value();
if (StringUtils.isEmpty(methodDescription)) {
// ------------------------
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
methodDescription = ms.getName();
}
return methodDescription;
}
private String appendMessage(ProceedingJoinPoint joinPoint, ApiLog apiLog, String methodDescription) {
StringBuilder message = new StringBuilder(methodDescription + ",开始执行!");
if (apiLog.showParams()) {
message.append("方法参数为:");
// 获取参数对象
Object[] params = joinPoint.getArgs();
// 遍历参数对象,拼接信息
for (Object param : params) {
message.append("\n")
.append(param.getClass().getName())
.append(":")
.append(param.toString());
}
}
return message.toString();
}
}