目录
1.什么是AOP
2.AOP的相关概念
3.SpringAOP 的使用
4.SpringAOP 的实现原理
AOP(Aspect Oriented Programming):面向切面编程,是一种思想,是对某一类事情进行集中处理
AOP用于将代码的横切关注点(cross-cutting concerns)从业务逻辑中分离出来,以便更好地维护和复用。
AOP是一种思想,SpringAOP是一个框架,是其具体的实现,关系和IoC与DI相似
为什么要用AOP
在软件开发中,横切关注点是指与业务无关但又必须被考虑的问题,如日志记录、异常处理、安全控制、事务管理等。这些问题通常散布在整个应用程序中,不仅增加了代码的复杂性,还使得代码难以扩展和修改。而 AOP 正是为了解决这种功能统一且使用地方较多的功能进行统一处理而生。
AOP可以扩充 多个对象 的某个能力,AOP可以是OOP(Object Oriented Programming,面向对象编程)的补充和完善
切面:切面可以看作是一种横切关注点的模块化。横切关注点指的是那些与业务逻辑无关但必须考虑的问题,例如权限检查、异常处理、日志记录等。将这些关注点从业务逻辑中分离出来,利用切面的技术实现,可以让开发人员更好地维护和复用代码
切面由三个部分构成:切点(Pointcut)、通知(Advice)和连接点(Join Point)
切点:相当于方法,定义一个拦截规则
通知:需要执行的具体内容,相当于方法具体实现,执行AOP的业务逻辑
连接点:可能会触发切点的点
通知:通知(Advice)是针对切点(Pointcut)定义的拦截器,它是实现横切关注点的具体动作
AOP 中共定义了五种通知类型:
这些通知类型可以以单独或组合的方式应用到切点上,以满足不同的业务需求。通过定义不同类型的通知组合,可以实现各种复杂的业务场景,从而提高代码的复用性和可维护性
1.添加框架支持
org.springframework.boot
spring-boot-starter-aop
2.7.11
添加到pom.xml中
2.创建切面
3.创建切点(定义拦截规则)
5.创建连接点
这里有两个连接点
前置通知@Before
后置通知@After
只有在拦截规则下的方法才触发了前置通知
Artcile中没有任何通知
环绕通知@Around
可以看出这里是使用了AOP代理
三个通知都执行的情况
这是通知的执行顺序,环绕通知是在执行最前面和最后面执行的
切点表达式说明
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
AspectJ 支持以下三种通配符:
*
:表示匹配任意数量的字符。例如,com.example.*
表示匹配 com.example 包下的所有类。
..
:表示匹配任意数量的包名或子包名,可以用在类名、方法名或者参数列表中。例如,com.example.demo..*
表示匹配 com.example.demo 包及其子包下的所有类。
+
:表示匹配指定类及其子类。例如,com.example.demo.controller.UserController+
表示匹配 UserController 类及其子类。
@Pointcut
注解:定义了一个切入点,它表达式的内容描述了需要被拦截的目标方法,该注解通常与其他 AOP 注解一起使用,用于声明哪些方法需要被切入。
*
:通配符,表示匹配任意返回类型的方法。
com.example.demo.controller.UserController
:表示目标类的完全限定名(包名称+类名)。*
:通配符,表示匹配 UserController 类中的任意方法
(..)
:表示任意参数类型和个数,即匹配任何参数列表的方法。
Spring AOP 中的 Pointcut 表达式可以使用 AspectJ 的语法格式。AspectJ 支持的语法格式非常灵活,可以匹配类、方法、参数等多种元素,大致格式如下:
execution([访问修饰符模式][返回类型模式] 包名模式.类名模式.方法名模式(参数模式) [异常模式])
访问修饰符模式:public、protected、private、*(任意访问修饰符)等。能省略
返回类型模式:用于描述被代理方法的返回类型,例如 int、java.lang.Object、*(任意返回类型)等。不可省略
包名模式、类名模式和方法名模式:用于描述被代理类及其方法的名称,可使用通配符进行模糊匹配。(*+..)
异常模式:用于描述被代理方法可能抛出的异常类型,例如 java.lang.Exception、*(任意异常类型)。通常情况省略
什么是代理?
比如在博客系统中,调用者需要调用一个对象,前提必须是在登陆情况下,才能进行调用.因此如果直接调用对象,目标对象必须实现登录功能,从而不能专注于对象自身的功能
为了解决这一问题,给目标对象加了一层代理,代理先进行是否登录判断,然后没有问题了就交给目标对象完成功能.目标对象就能专注于实现自己的功能.
动态代理和静态代理的区别
相同点:都是代理模式的实现方式,目的都是为了在不修改原有代码的情况下,增强已有类(也称为目标对象)的功能
不同点:
静态代理是通过手动编写代理类来实现的,这个代理类和目标对象实现同一个接口或继承同一个抽象类,并在代理类中对目标对象的方法进行增强逻辑的编写。静态代理需要在编译期间确定代理对象,因此代理对象的类型是固定的,如果要代理的类很多,就需要写很多代理类,会使代码冗余且难以维护。
动态代理则是利用 Java 反射机制在运行时动态生成代理类的实例对象来实现的。动态代理可以在程序运行时根据需要动态生成代理类,无需手动编写特定的代理类,省去了大量重复的代码,更加灵活。Java 提供了两种动态代理实现方式:JDK 动态代理和 CGLIB 动态代理。其中,JDK 动态代理只能代理实现了接口的类,而 CGLIB 可以代理所有类。
Spring AOP实现原理
Spring AOP 是基于动态代理实现的。在 Spring 容器启动时,会扫描标有特定注解的类(比如 @Service、@Component 等),并根据这些类及其方法生成代理类。
生成代理类过程中,Spring AOP 会根据切入点表达式(Pointcut Expression)来确定哪些方法需要被织入(Weaving)增强逻辑。增强逻辑通常是指一段代码(比如日志记录、性能统计、事务管理等),这些代码可以在目标方法执行前、执行后或抛出异常时自动触发。
Spring AOP 通过 JDK 动态代理和 CGLIB(Code Generation Library)动态生成代理类。对于实现了接口的目标对象,Spring AOP 使用 JDK 动态代理;对于没有实现接口的目标对象,Spring AOP 使用 CGLIB 生成代理类。
当调用者调用目标对象的方法时,Spring AOP 会将调用转发给代理对象,并在代理对象中执行增强逻辑。因此,调用者不需要知道任何增强逻辑的实现细节,只需要像平常一样调用目标对象的方法即可,值得注意的是,代理类是在运行时生成的,这意味着我们可以在不修改源代码的情况下为程序添加额外的功能,这就是 AOP 的优势所在
Spring AOP 的实现原理就是:基于动态代理,根据切入点表达式确定需要增强的方法,并在代理对象中执行增强逻辑
动态代理
动态代理也是一种设计模式,是在class代码运行期,动态的织入字节码.上文说过,Spring AOP 通过 JDK 动态代理和 CGLIB(Code Generation Library)动态生成代理类,我们一一来看。
JDK 动态代理
JDK 动态代理是通过运用 Java 反射机制,在运行时动态地生成代理类和代理对象,实现对目标对象的增强操作
JDK 动态代理的具体实现过程如下:
1.定义一个接口,其中包含了需要增强的方法
2.定义一个 InvocationHandler 类,该类实现 InvocationHandler 接口,并实现了 invoke() 方法,在该方法中对目标方法进行增强
3.使用 Proxy 类的静态方法 newProxyInstance() 创建代理对象,并传入 ClassLoader、Class[] 和 InvocationHandler 对象
4.使用代理对象调用方法时,会被重定向到 InvocationHandler 的 invoke() 方法中,从而可以对目标方法进行增强操作
JDK 动态代理是基于接口进行代理的,因此只能对实现了接口的目标对象进行代理增强。而如果目标对象没有实现任何接口,就无法使用 JDK 动态代理进行增强。为了解决这个问题,可以使用 CGLIB 动态代理,它能够针对任意的类来生成代理对象。
CGLIB 动态代理
CGLIB 动态代理使用 ASM (Java 字节码操作框架)操作字节码,生成目标对象的子类(通过实现代理类的子类实现动态代理),并重写需要增强的方法,从而实现对目标对象的方法进行增强
CGLIB 动态代理不需要像 JDK 动态代理一样,需要依赖接口来进行代理,因此,CGLIB 动态代理可以对任意的普通 Java 类进行代理,不需要目标对象实现接口
CGLIB 动态代理不能代理被final修饰的类,因为final修饰的类不能生成子类
CGLIB 动态代理的使用步骤如下:
1.定义一个切面类,该类需要包含需要增强的方法
2.创建一个 MethodInterceptor 对象,该对象实现了 intercept() 方法,在该方法中,可以对目标方法进行增强操作
3.使用 Enhancer 对象创建目标对象的子类,并设置其父类、回调方法以及构造函数参数等信息
4.使用代理对象调用方法时,会被重定向至 MethodInterceptor 的 intercept() 方法中进行处理,并执行增强操作。当代理对象调用方法执行完毕后,intercept() 方法会返回执行结果
JDK 动态代理和 CGLIB 动态代理主要区别
1.代理对象的生成方式不同
JDK 动态代理是基于接口的代理,它通过反射机制动态地创建目标接口的代理对象,使用代理对象来调用目标方法。而 CGLIB 动态代理则是基于类的代理,通过继承目标对象,并重写其中需要增强的方法,来实现对目标方法的增强
2.支持代理的对象类型不同
JDK 动态代理只能代理实现了接口的类,而 CGLIB 动态代理支持对没有实现接口的类的代理
3.性能有所差异
相对于 CGLIB 动态代理,JDK 动态代理在性能方面表现更好,主要因为 JDK 动态代理使用了轻量级的代理方式,可以在运行时动态地生成目标接口的代理对象,而不需要对字节码进行操作。
4.生成代理类的方式不同
CGLIB 动态代理通过对字节码的操作,实现对目标类的增强;而 JDK 动态代理则是通过反射机制动态地生成代理类