本篇文章旨在整理个人对SpringAOP模块的一个整体掌握。主要会围绕AOP的基本概念、专业术语、使用方法、使用特性等方面进行展开介绍。
基本概念
OOP(面向对象编程):OOP的关注点是对象,主要考虑的是对象的属性和行为
AOP(面向切面编程):AOP是OOP的一个补充,关注点是那些业务无关但是多个类共同拥有的行为逻辑,AOP主要作用就是将这些共同拥有的行为逻辑抽象成为一个模块,以减少代码重复。抽象出来的模块又被称之为切面。
专业术语
- 切面(Aspect):是多个类公共行为逻辑的一个模块化,在Spring中由
@Component
和@Aspect
共同标记的类即为切面,切面中包含切入点(Pointcut)和通知(Advice) - 连接点(Join point):所有可能被织入通知的点,在SpringAOP中所有运行中的方法都是可以被织入通知的,在SpringAOP中连接点就是所有运行中的方法
- 切入点(Pointcut):切入点是真正被织入通知的方法。在SpringAOP中通过@Pointcut指定规则,定义哪些方法是切入点
- 通知(Advice):定义公共行为的代码,织入到切入点,SpringAOP中有5种通知类型,可以织入到切入点的不同地方,
- 前置通知(@Before):在方法执行前执行
- 后置通知(@AfterReturning):在方法正常返回后执行
- 环绕通知(@Around):可以方法的执行前后定义逻辑,也可以控制方法执行
- 异常通知(@AfterThrowing):在方法抛出异常时执行
- 最终通知(@After):最终执行的方法不管方法是否正常退出
- 目标对象(Target object):被AOP代理对象所代理的对象,目标对象中的方法是需要织入通知的
- AOP代理(AOP proxy):为了能够让目标对象的方法织入通知,需要生成目标对象的代理对象,这个代理对象目的是完成AOP操作,所以叫AOP代理
织入(Weaving):将通知增强到切入点中的动作。有以下三种织入时机
- 基于XML方式:
基于注解方式:@EnableAspectJAutoProxy
切面、切入点、通知定义
```yaml @Component @Aspect public class MyAspect { /**
切入点规则定义 / @Pointcut(“execution( com.gao..(..))”) public void myPointCut(){ }
@Before(“execution( com.gao..*(..))”) public void before(){ System.out.println(“before advice”); }
@After(value = “myPointCut()”) public void after(){ System.out.println(“after”); }
@AfterReturning(“myPointCut()”) public void afterReturning(){ System.out.println(“after returning”); }
@AfterThrowing(“myPointCut()”) public void afterThrowing(){ System.out.println(“after throwing”); }
@Around("myPointCut()")
public void around(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("around 前");
try {
proceedingJoinPoint.proceed();
System.out.println("around try 后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("around throw");
}finally {
System.out.println("around finally");
}
System.out.println("around 后");
}
}
<a name="WMsUl"></a>
## 基于XML配置
由于现在都是SpringBoot,基于XML方式这里就不费时间写了
<a name="ZcTMc"></a>
# 使用特性
<a name="ahweH"></a>
## 执行顺序
切面的执行顺序可以通过`@Order`指定,下面来看下通知的执行顺序
1. 单切面
![image.png](https://cdn.nlark.com/yuque/0/2022/png/1609516/1651493496270-82435aab-530f-43a5-99d1-96d7a7a60543.png#clientId=u933ebcbd-74c1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=525&id=u6a163202&margin=%5Bobject%20Object%5D&name=image.png&originHeight=656&originWidth=762&originalType=binary&ratio=1&rotation=0&showTitle=false&size=115639&status=done&style=none&taskId=u3d55ca22-81fc-4fc9-8685-0c903a1b2f0&title=&width=609.6)
2. 多切面
![image.png](https://cdn.nlark.com/yuque/0/2022/png/1609516/1651493937863-aeaf0b55-42f5-4064-aed9-29b459b337c1.png#clientId=u933ebcbd-74c1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=349&id=u363201f1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=436&originWidth=866&originalType=binary&ratio=1&rotation=0&showTitle=false&size=99204&status=done&style=none&taskId=uc0080e14-fbb2-4ccb-bc31-b56edc504b2&title=&width=692.8)
<a name="UVfnO"></a>
## 如何修改入参
1. 对于@Around通知,可以控制方法的运行和传入的参数,所以很好解决
2. 对于其他通知类型
1. 我们可以拿到目标方法的参数但是无法修改其值
2. 若是引用类型我们是可以改对象的里面属性的,但是无法直接将其对象替换
![image.png](https://cdn.nlark.com/yuque/0/2022/png/1609516/1651500595753-5b0686fb-36e3-4d82-8dde-87daf8353285.png#clientId=u933ebcbd-74c1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=205&id=ucf90aab6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=256&originWidth=1295&originalType=binary&ratio=1&rotation=0&showTitle=false&size=72985&status=done&style=none&taskId=u2f2e2a76-6a35-4c87-9a81-0a765dffa68&title=&width=1036)
<a name="ZtYF4"></a>
## 如何获取入参
获取入参的方式官方提供了1个,这边我们还可以通过自定义注解+JoinPoint+SpEL表达式的方式获取
1. 方式一:args表达式
![](https://cdn.nlark.com/yuque/0/2022/png/1609516/1651500118080-530e145c-635f-49f1-80f3-bad6db7d4198.png#crop=0&crop=0&crop=1&crop=1&from=url&id=puJHw&margin=%5Bobject%20Object%5D&originHeight=186&originWidth=875&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
2. 方式二:自定义注解+JointPoint+SpEL表达式
1. 自定义注解中声明一个字段用于指定SpEL表达式
2. 将JoinPoiont的所有参数名和参数值放入SpELContext中
3. 通过注解中的SpEL表达式获取参数中的值
```yaml
package com.yurun.micro.dms.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Sap接口注解
*
* @author GaoXi
* @date 2022/4/28 19:38
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SapInterface {
/**
* 指定全量标识字段: #参数名.全量标识字段
*/
String fullSign() default "";
}
@SapInterface(fullSign = "#maraMasterRequest.zzall")
@Override
public R<List<SapOtMara>> maraMasterList(MaraMasterRequest maraMasterRequest) {
return remoteSapDmsService.maraMasterList(maraMasterRequest);
}
/**
* 获取是否全量标识字符串
*
* @param joinPoint 连接点
* @param fullSign #参数名.字段名
* @return 0-非全量 1-全量
*/
private String getIsFullSign(ProceedingJoinPoint joinPoint, String fullSign) {
// 参数值
Object[] args = joinPoint.getArgs();
// 参数名
String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
// SpELContext初始化并解析表达式
Expression expression = spelExpressionParser.parseExpression(fullSign);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < args.length; i++) {
context.setVariable(argNames[i], args[i]);
}
return Objects.requireNonNull(expression.getValue(context)).toString();
}
如何定义一个切入点
- execution:用于定义匹配某些方法
- public 包名.类名.方法名(参数类型,参数类型) 声明异常类型
- within:用于定义在某些类下的方法
- 包名.类名
- this:暂不理解
- 类全限定名
- target:暂不理解
- 类全限定名
- args:匹配方法传入参数类型为指定类型的方法
- 类型,类型
- @target:暂不理解
- @args:指定方法参数中有指定注解的
- @within:类上有指定注解的类下所有方法
- @annotation:匹配有特定注解的方法
常见特殊符号
..
:所有参数类型.*
:一般指下一级的所有东西..*
:一般指所有下一级下的所有东西如何定义通知
Before通知
before通知没有什么特别的,正常定义就好
AfterReturning通知
afterReturning可以接收一个返回值
AfterThrowing通知
afterThrowing通知可以接收一个异常
After通知
Around通知
around的可以控制方法的执行,需要在参数中加入ProcessJoinPoint
且在参数的首位
JoinPoint方法
所有的通知都可以将JoinPoint类放在方法的首个参数中。JoinPoint提供了如下方法
- 方式二:通过@annotation传入方法上的注解
动态代理实现方式选择
Spring的AOP动态代理实现方式有两种:CGLIB和JDK动态代理
- 若被代理的目标对象没有实现接口,则使用CGLIB
- 若被代理的目标对象实现了接口,则使用JDK动态代理
- 可以通过强制使用CGLIB动态代理,其方式是将proxyTargetClass设置为true
@EnableAspectJAutoProxy(proxyTargetClass = true)