侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 20 个标签
  • 累计收到 3 条评论

Spring AOP 学习笔记

林贤钦
2020-12-15 / 0 评论 / 0 点赞 / 625 阅读 / 7,469 字
温馨提示:
本文最后更新于 2021-03-01,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Spring AOP 学习笔记

AspectJ TM编程指南

AspectJ TM 5开发套件开发人员的笔记本

Spring文档

使用Spring进行面向方面的编程

彻底征服 Spring AOP 之 理论篇

彻底征服 Spring AOP 之 实战篇

Spring AOP使用讲解

AOP简介

AOP是Aspect Oriented Programming 的缩写,意思是“面向切面编程”,它是预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。可以认为AOP是对OOP(面向对象编程)的补充,主要使用在日志记录,性能分析,安全控制等场景,使用AOP可以使得业务逻辑各部分之间的耦合度降低,只专注于各自的业务逻辑实现,从而提高程序的可读性及维护性。

AOP的重要概念

通知(Advice)

在AOP的术语中,切面要完成的工作被成为通知,通知定义了切面是什么,以及何时使用。

Spring切面有五种类型的通知

  1. 前置通知(Before):在目标方法被调用之前调用通知功能
  2. 后置通知(After):在目标方法完成之后调用通知,此时不关心方法的输出结果是什么
  3. 返回通知(After-returning):在目标方法成功执行之后调用通知
  4. 异常通知(After-throwing):在目标方法抛出异常后调用通知
  5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

连接点(Join point)

a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理. 在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点。

连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、修改某个字段时。

切点(Pointcut)

在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.

切点是为了缩小切面所通知的连接点的范围,即切面在何处执行。我们通常使用明确的类和方法名称,或者利用正则表达式定义所匹配的类和方法名称来指定切点。

join point和pointcut的区别

在 Spring AOP 中, 所有的方法执行都是 join point. 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 因此 join point 和 point cut 本质上就是两个不同纬度上的东西.

advice 是在 join point 上执行的, 而 point cut 规定了哪些 join point 可以执行哪些 advice

切面(Aspect)

切面是advice(通知)和pointcount(切点)的结合。通知和切点共同定义了切面的全部内容:它是什么,在何时和何处完成其功能。

引入(Introduction)

为一个类型添加额外的方法或字段. Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现.

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。

切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里,有以下几个点可以进行织入:

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

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

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

AOP, AspectJ, Spring AOP

AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。

AOP 的实现并不是因为 Java 提供了什么神奇的钩子,可以把方法的几个生命周期告诉我们,而是我们要实现一个代理,实际运行的实例其实是生成的代理类的实例。

作为 Java 开发者,我们都很熟悉 AspectJ 这个词,甚至于我们提到 AOP 的时候,想到的往往就是 AspectJ,即使你可能不太懂它是怎么工作的。这里,我们把 AspectJ 和 Spring AOP 做个简单的对比:

Spring AOP:

  • 它基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。
  • Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖
  • Spring 的 IOC 容器和 AOP 都很重要,Spring AOP 需要依赖于 IOC 容器来管理
  • Spring AOP 只能作用于 Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法
  • Spring 提供了 AspectJ 的支持,后面我们会单独介绍怎么使用,一般来说我们用纯的 Spring AOP 就够了
  • 很多人会对比 Spring AOP 和 AspectJ 的性能,Spring AOP 是基于代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 那么好

AspectJ:

  • AspectJ 出身也是名门,来自于 Eclipse 基金会
  • 属于静态织入,它是通过修改代码来实现的,它的织入时机可以是
    1. Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
    2. Post-compile weaving: 也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
    3. Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。(1)自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。(2)在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar
  • AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。
  • 因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的。

Spring 对AOP的支持

动态代理

Spring AOP构建在动态代理之上,也就是说,Spring运行时会为目标对象动态创建代理对象。代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理类拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。

织入切面时机

通过在代理类中包裹切面,Spring在运行期把切面织入到Spring 管理的bean中,也就是说,直到应用需要被代理的bean时,Spring才会创建代理对象。

因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入Spring AOP切面。

连接点限制

Spring只支持方法级别的连接点,如果需要字段级别或者构造器级别的连接点,可以利用AspectJ来补充Spring AOP的功能。

开启@AspectJ 支持

使用@Configuration 支持@AspectJ 的时候,需要添加 @EnableAspectJAutoProxy 注解,就像下面例子展示的这样来开启 AOP代理

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    
}

也可以使用XML配置来开启@AspectJ 支持

<aop:aspectj-autoproxy/>

声明一个切面

在启用了@AspectJ支持的情况下,在应用程序上下文中定义的任何bean都具有@AspectJ方面的类(具有@Aspect注释),Spring会自动检测并用于配置Spring AOP

使用XML 配置的方式定义一个切面

<aop:aspect />

使用注解的方式定义一个切面

@Aspect
public class MyAspect {}

定义一个切点

一个切点由两部分组成:包含名称和任何参数以及切入点表达式的签名,该表达式能够确定我们想要执行的方法。在@AspectJ注释风格的AOP中,切入点表达式需要用@Pointcut注解标注

这个表达式作为方法的签名,它的返回值必须是 void

@Pointcut("execution(* com.linxianqin.Test.transfer(..))") // 切入点表达式
private void pointcut() {}// 方法签名

表达式的意义:

pointcut切入点
execution()用于匹配是连接点的执行方法
*任意返回值
com.linxianqin.Aop类的包路径
transfer(..)方法名,两点指任意参数

使用XML配置来配置切点

<aop:config>
	<aop:aspect ref = "">
  	<aop:poincut id = "" expression="execution(** com.linxianqin.Aop.transfer(......))"/>
  </aop:aspect>
</aop:config>

通知是和切入点表达式相互关联,用于在方法执行之前,之后或者方法前后,方法返回,方法抛出异常时调用通知的方法,切入点表达式可以是对命名切入点的简单引用,也可以是在适当位置声明的切入点表达式。下面以一个例子来演示一下这些通知都是如何定义的:

切点标志符(designator)

AspectJ 标志符描述
arg()限制连接点匹配参数为指定类型的执行方法
@args()限制连接点匹配参数由指定注解标注的执行方法
execution()用于匹配是连接点的执行方法
this()限制连接点匹配的AOP代理的bean引用为指定类型的类
target限制连接点匹配目标对象为指定类型的类
@target()限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
within()限制连接点匹配指定的类型
@within()限制连接点匹配指定注解所标注的类型
@annotationn限定匹配带有指定注解的连接点

常见的切点表达式

匹配方法签名

    // 匹配指定包中的所有的方法 
    execution(* com.xys.service.*(..))  
    // 匹配当前包中的指定类的所有方法 
    execution(* UserService.*(..))  
    // 匹配指定包中的所有 public 方法 
    execution(public * com.xys.service.*(..))  
    // 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法 
    execution(public int com.xys.service.*(..))  
    // 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法 
    execution(public int com.xys.service.*(String name, ..))

匹配类型签名

    // 匹配指定包中的所有的方法, 但不包括子包 
    within(com.xys.service.*)  
    // 匹配指定包中的所有的方法, 包括子包 
    within(com.xys.service..*)  
    // 匹配当前包中的指定类中的方法 
    within(UserService)   
    // 匹配一个接口的所有实现类中的实现的方法 
    within(UserDao+)

匹配 Bean 名字

    // 匹配以指定名字结尾的 Bean 中的所有方法 
    bean(*Service)

切点表达式组合

    // 匹配以 Service 或 ServiceImpl 结尾的 
    bean bean(*Service || *ServiceImpl)  
    // 匹配名字以 Service 结尾, 并且在包 com.xys.service 中的 bean 
    bean(*Service) && within(com.xys.service.*)

声明 advice

advice 是和一个 pointcut 表达式关联在一起的, 并且会在匹配的 join point 的方法执行的前/后/周围 运行. pointcut 表达式可以是简单的一个 pointcut 名字的引用, 或者是完整的 pointcut 表达式.

1608038439(1)

0

评论区