写点什么

🍃【Spring 原理系列】让你知道真正的“AOP”

发布于: 2021 年 05 月 16 日
🍃【Spring原理系列】让你知道真正的“AOP”

🍃 每日一句

自己不能胜任的事情,切莫轻易答应别人,一旦答应了别人,就必须实践自己的诺言。

🍃 前提概要

AOP(面向切面编程)是一种编程范式,与语言无关,是一种程序设计思想。

面向切面编程(AOP)Aspect Oriented Programming,切面编程的目的是为了把通用逻辑从业务辑

分离出来。

🍃 本篇要点

  • 简单介绍 SpringAOP 的相关知识点:关键术语,通知类型,切入点表达式等等

  • 以及对 SpringAOP 的相关的知识点包括源码解析:

🍃 实际案例

  • 记录日志

  • 监控性能

  • 权限控制

  • 事务管理

  • 缓存管理

🍃 面向切面

🍃 常用术语

  • 切面(Aspect):定义的专注于提供辅助功能的模块,如安全管理,日志信息、缓存管理器等。

  • 连接点(JoinPoint)切面代码可以通过连接点切入到正常业务之中,图中每个业务方法的每个点都是连接点

  • 切入点(PointCut)切面不需要通知所有的连接点,而在连接点的基础之上增加切入的规则,选择需要增强的点,最终真正通知的点就是切入点

  • Spring 和 Aspectj 采用的是 Joinpoint 的表达式,表示拦截哪些方法。一个 Pointcut 对应多个 Joinpoint。

  • 通知方法(Advice)切面需要执行的工作实际要切入的逻辑

  • Before Advice:在方法前切入。

  • After Advice:在方法后切入,抛出异常时也会切入。

  • AfterReturning Advice:在方法返回后切入,抛出异常则不会切入。

  • AfterThrowingAdvice:在方法抛出异常之后切入。

  • Around Advice:在方法执行前后环绕切入,可以中断或忽略原有流程的执行。

  • 织入(Weaving):切面应用到目标对象并创建代理对象的过程,SpringAOP 选择再目标对象的运行期动态创建代理对

  • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ 的织入编译器就是以这种方式织入切面的。

  • 类加载期:切面在目标类加载到 JVM 时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5 的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。

    运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。Spring AOP 就是以这种方式织入切面的。

  • 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加方法或字段。

🍃 表达式示意图

🍃 切入点表达式

连接点增加切入规则就相当于定义了切入点,切入点表达式分为很多种,如下图所示。


📒 execute 表达式

execution(modifiers-pattern ? ret-type-pattern declaring-type-pattern ? name-pattern(param-pattern) throws-pattern?)


上面的就是 execution 表达式的格式,execution 匹配的就是连接点(Joinpoint,一般是方法),看上面的表达式 execution 是固定的,方法的修饰符是可选的,返回类型是必须的,定义的全限类型也是可选的,名称是必须的,参数是必须的,这些都可以使用通配符。


✒️ 实际案例

  • 拦截任意公共方法

execution ( public * *(..) )

  • 拦截以 set 开头的任意方法

execution(* set*(..))

  • 拦截类或者接口中的方法

拦截 AccountService(类、接口)中定义的所有方法

execution(* com.xyz.service.AccountService.*(..))

  • 拦截包中定义的方法,不包含子包中的方法:拦截 com.xyz.service 包中所有类中任意方法,不包含子包中的类

execution(* com.xyz.service..(..))

  • 拦截包或者子包中定义的方法:拦截 com.xyz.service 包或者子包中定义的所有方法

execution(* com.xyz.service...(..))


📒 within 表达式

表达式格式 1:包名.* 或者 包名..*

  • 拦截包中任意方法,不包含子包中的方法

within(com.xyz.service.*)

拦截 service 包中任意类的任意方法

  • 拦截包或者子包中定义的方法

within(com.xyz.service..*)

拦截 service 包及子包中任意类的任意方法


📒 this 表达式

代理对象为指定的类型会被拦截,目标对象使用 aop 之后生成的代理对象必须是指定的类型才会被拦截,注意是目标对象被代理之后生成的代理对象和指定的类型匹配才会被拦截。

this(com.xyz.service.AccountService)

this 匹配的是代理对象的类型,例如存在一个接口 B,使用 this("B"),如果某个类 A 的 JDK 代理对象类型为 B,则 A 实现的接口 B 的方法会作为切点进行织入。

this 的匹配结果和最终生成的代理对象互不干涉,对于 this(M),当 M 为类时,若一个类的 CGLIB 代理类型为 M,该类的所有方法作为连接点织入到 CGLIB 代理对象中,当 M 为接口时,若一个类的 JDK 代理类型为 M,该类实现的接口方法作为连接点织入 JDK 代理对象中,this 的匹配结果不会影响 spring 生成代理对象的方式。

若一个类继承了接口,则使用 JDK 代理,否则使用 CGLIB 代理,这就会出现下列情况:

A 继承了 B 接口,this("A")会匹配到 A 的 CGLIB 代理,接着将 A 的所有方法织入到 A 的 CGLIB 代理对象中,但是因为 A 继承了 B 接口。


📒 target 表达式

目标对象为指定的类型被拦截

target(com.xyz.service.AccountService)

目标对象为 AccountService 类型的会被代理

target 匹配目标对象的类型,即被代理对象的类型,例如 A 继承了 B 接口,则使用 target("B"),target("A")均可以匹配到 A。


📒 this 和 target 的不同点

  • this 作用于代理对象,target 作用于目标对象

  • this 表示目标对象被代理之后生成的代理对象和指定的类型匹配会被拦截,匹配的是代理对象

  • target 表示目标对象和指定的类型匹配会被拦截,匹配的是目标对象


📒 args 表达式

匹配方法中的参数

@Pointcut("args(com.ms.aop.args.demo1.UserModel)")

匹配只有一个参数,且类型为 com.ms.aop.args.demo1.UserModel

匹配多个参数

args(type1,type2,typeN)

匹配任意多个参数

@Pointcut("args(com.ms.aop.args.demo1.UserModel,..)")

匹配第一个参数类型为 com.ms.aop.args.demo1.UserModel 的所有方法, .. 表示任意个参数

有且只有一个 Serializable 参数的方法

args(java.io.Serializable)

只要这个参数实现了 java.io.Serializable 接口就可以,不管是 java.io.Serializable 还是 Integer,还是 String 都可以。


📒 @target 表达式

匹配的目标对象的类有一个指定的注解

@target(com.ms.aop.jtarget.Annotation1)

目标对象中包含 com.ms.aop.jtarget.Annotation1 注解,调用该目标对象的任意方法都会被拦截

目标(target)使用了 @Transactional 注解的方法

@target(org.springframework.transaction.annotation.Transactional)

目标类(target)如果有 Transactional 注解的所有方法


📒 @within 表达式

@within(注解类型全限定名)匹配所有持有指定注解的类里面的方法, 即要把注解加在类上. 在接口上声明不起作用 。

指定匹配必须包含某个注解的类里的所有连接点

@within(com.ms.aop.jwithin.Annotation1)

声明有 com.ms.aop.jwithin.Annotation1 注解的类中的所有方法都会被拦截,所有类的所有的方法都会执行 aop 方法


📒 @target 和 @within 的不同点

@target(注解 A):判断被调用的目标对象中是否声明了注解 A,如果有,会被拦截

@within(注解 A): 判断被调用的方法所属的类中是否声明了注解 A,如果有,会被拦截

@target 关注的是被调用的对象,@within 关注的是调用的方法所在的类


📒 @target 和 @within 的总结

  • 父类有注解,但子类没有注解的话,@within 和 @target 是不会对子类生效的。

  • 子类没有注解的情况下,只有没有被重写的有注解的父类的方法才能被 @within 匹配到。

  • 如果父类无注解,子类有注解的话,@target 对父类所有方法生效,@within 只对重载过的方法生效。


📒 @annotation 表达式

匹配有指定注解的方法(注解作用在方法上面)

@annotation(com.ms.aop.jannotation.demo2.Annotation1)

被调用的方法包含指定的注解


📒 @args 表达式

方法参数所属的类型上有指定的注解,被匹配

注意:是方法参数所属的类型上有指定的注解,不是方法参数中有注解。

  • 匹配 1 个参数,且第 1 个参数所属的类中有 Anno1 注解

@args(com.ms.aop.jargs.demo1.Anno1)

  • 匹配多个参数,且多个参数所属的类型上都有指定的注解

@args(com.ms.aop.jargs.demo1.Anno1,com.ms.aop.jargs.demo1.Anno2)

  • 匹配多个参数,且第一个参数所属的类中有 Anno1 注解

@args(com.ms.aop.jargs.demo2.Anno1,..)


用户头像

我们始于迷惘,终于更高水平的迷惘。 2020.03.25 加入

🏆 【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 🤝未来我们希望可以共同进步🤝

评论

发布
暂无评论
🍃【Spring原理系列】让你知道真正的“AOP”