AOP介绍
为什么有AOP?
AOP:Aspect Oriented Programming面向切面编程。
AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP相关术语
连接点 joinpoint
这是一个纯逻辑概念,不是语法定义的,指那些被拦截到的点。在 Spring 中,指可以被动态代理拦截目标类的方法。
切入点 pointcut
定位连接点的方式,或者可以理解成被选中的连接点。
是一个表达式,比如execution(* com.spring.service.impl.. (..))。符合条件的每个方法都是一个具体的连接点。
通知(增强)
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
前置通知:在被代理的目标方法前执行
返回通知:在被代理的目标方法成功结束后执行
异常通知:在被代理的目标方法异常结束后执行
后置通知:在被代理的目标方法最终结束后执行
环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
切面 aspect
切入点和通知的结合。是一个类。
目标 target
被代理的目标对象。
代理 proxy
向目标对象应用通知之后创建的代理对象。
Spring AOP
Spring AOP顶层技术
动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口。
cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口。
AspectJ:早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。
使用案例
引入依赖
1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId> org.springframework</groupId>
<artifactId> spring-aop</artifactId>
<version> 6.0.6</version>
</dependency>
<dependency>
<groupId> org.springframework</groupId>
<artifactId> spring-aspects</artifactId>
<version> 6.0.6</version>
</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
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
// @Before注解:声明当前方法是前置通知方法
// value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
@Before ( value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))" )
public void printLogBeforeCore () {
System . out . println ( "[AOP前置通知] 方法开始了" );
}
@AfterReturning ( value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))" )
public void printLogAfterSuccess () {
System . out . println ( "[AOP返回通知] 方法成功返回了" );
}
@AfterThrowing ( value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))" )
public void printLogAfterException () {
System . out . println ( "[AOP异常通知] 方法抛异常了" );
}
@After ( value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))" )
public void printLogFinallyEnd () {
System . out . println ( "[AOP后置通知] 方法最终结束了" );
}
}
开启aspectj注解支持
1
2
3
4
5
6
@Configuration
@ComponentScan ( basePackages = "com.atguigu" )
//作用等于 <aop:aspectj-autoproxy /> 配置类上开启 Aspectj注解支持!
@EnableAspectJAutoProxy
public class MyConfig {
}
获取通知细节信息
JointPoint接口
需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。
要点1:JoinPoint 接口通过 getSignature() 方法获取目标方法的签名(方法声明时的完整信息)
要点2:通过目标方法签名对象获取方法名
要点3:通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// @Before注解标记前置通知方法
// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
@Before ( value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))" )
public void printLogBeforeCore ( JoinPoint joinPoint ) {
// 1.通过JoinPoint对象获取目标方法签名对象
// 方法的签名:一个方法的全部声明信息
Signature signature = joinPoint . getSignature ();
// 2.通过方法的签名对象获取目标方法的详细信息
String methodName = signature . getName ();
System . out . println ( "methodName = " + methodName );
int modifiers = signature . getModifiers ();
System . out . println ( "modifiers = " + modifiers );
String declaringTypeName = signature . getDeclaringTypeName ();
System . out . println ( "declaringTypeName = " + declaringTypeName );
// 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object [] args = joinPoint . getArgs ();
// 4.由于数组直接打印看不到具体数据,所以转换为List集合
List < Object > argList = Arrays . asList ( args );
System . out . println ( "[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList );
}
方法返回值
1
2
3
4
5
6
7
8
9
10
11
12
// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning (
value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))" ,
returning = "targetMethodReturnValue"
)
public void printLogAfterCoreSuccess ( JoinPoint joinPoint , Object targetMethodReturnValue ) {
String methodName = joinPoint . getSignature (). getName ();
System . out . println ( "[AOP返回通知] " + methodName + "方法成功结束了,返回值是:" + targetMethodReturnValue );
}
异常对象捕捉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// @AfterThrowing注解标记异常通知方法
// 在异常通知中获取目标方法抛出的异常分两步:
// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
@AfterThrowing (
value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))" ,
throwing = "targetMethodException"
)
public void printLogAfterCoreException ( JoinPoint joinPoint , Throwable targetMethodException ) {
String methodName = joinPoint . getSignature (). getName ();
System . out . println ( "[AOP异常通知] " + methodName + "方法抛异常了,异常类型是:" + targetMethodException . getClass (). getName ());
}
切入点表达式
一图胜千言。
切入点表达式提取
1
2
3
4
5
6
// 切入点表达式重用
@Pointcut ( "execution(public int com.atguigu.aop.api.Calculator.add(int,int)))" )
public void declarPointCut () {}
@Before ( value = "declarPointCut()" )
public void printLogBeforeCoreOperation ( JoinPoint joinPoint ) {
优先级
使用 @Order 注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低