赞
踩
目录
3.4.1.5. AspectJ与Spring AOP的关系
搭建子模块:spring6-aop
声明计算器接口Calculator,包含加减乘除的抽象方法
- package com.sakurapaid.spring6.aop.demo;
-
- /**
- * 计算器接口,定义了基本的四则运算方法。
- */
- public interface Calculator {
- /**
- * 加法操作。
- *
- * @param i 第一个操作数。
- * @param j 第二个操作数。
- * @return 两个操作数相加的结果。
- */
- int add(int i, int j);
-
- /**
- * 减法操作。
- *
- * @param i 第一个操作数。
- * @param j 第二个操作数。
- * @return 两个操作数相减的结果。
- */
- int sub(int i, int j);
-
- /**
- * 乘法操作。
- *
- * @param i 第一个操作数。
- * @param j 第二个操作数。
- * @return 两个操作数相乘的结果。
- */
- int mul(int i, int j);
-
- /**
- * 除法操作。
- *
- * @param i 第一个操作数,作为被除数。
- * @param j 第二个操作数,作为除数。
- * @return 两个操作数相除的结果。
- */
- int div(int i, int j);
- }

- package com.sakurapaid.spring6.aop.demo;
-
- /**
- * 实现Calculator接口的计算类,提供基本的四则运算功能。
- */
- public class CalculatorImpl implements Calculator {
-
- /**
- * 实现加法操作。
- *
- * @param i 第一个操作数。
- * @param j 第二个操作数。
- * @return 两个操作数相加的结果。
- */
- @Override
- public int add(int i, int j) {
-
- int result = i + j; // 计算加法结果
-
- System.out.println("方法内部 result = " + result); // 打印结果
-
- return result; // 返回结果
- }
-
- /**
- * 实现减法操作。
- *
- * @param i 第一个操作数。
- * @param j 第二个操作数。
- * @return 两个操作数相减的结果。
- */
- @Override
- public int sub(int i, int j) {
-
- int result = i - j; // 计算减法结果
-
- System.out.println("方法内部 result = " + result); // 打印结果
-
- return result; // 返回结果
- }
-
- /**
- * 实现乘法操作。
- *
- * @param i 第一个操作数。
- * @param j 第二个操作数。
- * @return 两个操作数相乘的结果。
- */
- @Override
- public int mul(int i, int j) {
-
- int result = i * j; // 计算乘法结果
-
- System.out.println("方法内部 result = " + result); // 打印结果
-
- return result; // 返回结果
- }
-
- /**
- * 实现除法操作。
- *
- * @param i 第一个操作数。
- * @param j 第二个操作数。
- * @return 两个操作数相除的结果。
- */
- @Override
- public int div(int i, int j) {
-
- int result = i / j; // 计算除法结果
-
- System.out.println("方法内部 result = " + result); // 打印结果
-
- return result; // 返回结果
- }
- }

- package com.sakurapaid.spring6.aop.demo;
-
- /**
- * 实现Calculator接口的计算类,提供基本的四则运算功能,并通过日志记录方法的调用过程。
- */
- public class CalculatorLogImpl implements Calculator {
-
- @Override
- /**
- * 实现加法运算。
- * @param i 第一个加数。
- * @param j 第二个加数。
- * @return 两个数的和。
- */
- public int add(int i, int j) {
-
- System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
-
- int result = i + j; // 计算结果
-
- System.out.println("方法内部 result = " + result);
-
- System.out.println("[日志] add 方法结束了,结果是:" + result);
-
- return result;
- }
-
- @Override
- /**
- * 实现减法运算。
- * @param i 被减数。
- * @param j 减数。
- * @return 两个数的差。
- */
- public int sub(int i, int j) {
-
- System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
-
- int result = i - j; // 计算结果
-
- System.out.println("方法内部 result = " + result);
-
- System.out.println("[日志] sub 方法结束了,结果是:" + result);
-
- return result;
- }
-
- @Override
- /**
- * 实现乘法运算。
- * @param i 第一个乘数。
- * @param j 第二个乘数。
- * @return 两个数的积。
- */
- public int mul(int i, int j) {
-
- System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
-
- int result = i * j; // 计算结果
-
- System.out.println("方法内部 result = " + result);
-
- System.out.println("[日志] mul 方法结束了,结果是:" + result);
-
- return result;
- }
-
- @Override
- /**
- * 实现除法运算。
- * @param i 被除数。
- * @param j 除数。
- * @return 两个数的商。
- */
- public int div(int i, int j) {
-
- System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
-
- int result = i / j; // 计算结果
-
- System.out.println("方法内部 result = " + result);
-
- System.out.println("[日志] div 方法结束了,结果是:" + result);
-
- return result;
- }
- }

针对带日志功能的实现类,我们发现有如下缺陷:
解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。
解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。
代理模式的核心思想是为某个对象(称为目标对象或真实对象)创建一个替代品(称为代理对象)。当客户端请求与目标对象交互时,实际上是与代理对象打交道。代理对象可以透明地转发请求到目标对象,也可以在转发请求前或后添加额外的操作,如权限检查、日志记录、缓存、延迟加载、数据预处理等。这些附加功能通常不属于目标对象的核心业务逻辑,但对系统的整体功能或性能有重要影响。
代理模式的运作机制:
通俗例子说明:
相关术语解释:
总结起来,代理模式通过引入代理对象来控制对目标对象的访问,实现了职责分离和功能增强,有助于提高代码的可维护性和系统的灵活性。代理对象封装了非核心逻辑,使得这些逻辑可以独立于目标对象进行管理和扩展,同时保持了对客户端的透明性,即客户端无需关心是直接与目标对象交互还是通过代理进行交互。
静态代理是指在编写代码时,手动创建一个代理类来实现对目标对象的代理。这个代理类通常会遵循以下特点:
创建静态代理类:
- package com.sakurapaid.spring6.aop.demo;
-
- public class CalculatorStaticProxy implements Calculator {
-
- // 将被代理的目标对象声明为成员变量
- private final Calculator target;
-
- public CalculatorStaticProxy(Calculator target) {
- this.target = target;
- }
-
- @Override
- public int add(int i, int j) {
-
- // 附加功能由代理类中的代理方法来实现
- System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
-
- // 通过目标对象来实现核心业务逻辑
- int addResult = target.add(i, j);
-
- System.out.println("[日志] add 方法结束了,结果是:" + addResult);
-
- return addResult;
- }
-
- @Override
- public int sub(int i, int j) {
- return 0;
- }
-
- @Override
- public int mul(int i, int j) {
- return 0;
- }
-
- @Override
- public int div(int i, int j) {
- return 0;
- }
- }

静态代理虽然实现了解耦,即将附加功能(如日志)与核心业务逻辑分离,但它存在明显的局限性:
基于上述问题,提出了使用动态代理技术的需求。动态代理允许在运行时根据需要动态地创建代理对象,其优势在于:
举个通俗易懂的例子就是:
静态代理
想象一下,你正在经营一家小店,雇了一位员工(目标对象)专门负责算账。为了监督员工的工作,你找了一个朋友(代理对象)帮忙。朋友的任务是:
在这个例子中,你的朋友就是静态代理。编写了一个名为 CalculatorStaticProxy
的类,就像你朋友的角色一样。这个类实现了 Calculator
接口(就像你的朋友知道如何“算账”),并包含一个指向员工(target
)的引用。代理类的方法(如 add()
)包含了两部分任务:记录日志(附加功能)和调用员工的对应方法(核心业务逻辑)。
局限性
现在,问题来了:
动态代理
为了解决这些问题,你决定雇佣一个专业的审计团队(动态代理)。他们承诺:
在编程世界里,动态代理就像这个审计团队。它通过编程框架(如 Java 的 JDK 动态代理)在运行时自动为你生成代理对象。你只需要指定目标对象(员工),框架会自动生成一个代理对象(虚拟朋友),这个代理对象能执行附加功能(日志记录)并调用目标对象的方法(算账)。这样,无论有多少个目标对象(分店员工),都可以用同一个动态代理机制来处理,日志逻辑也集中在一个地方管理,大大提高了代码的复用性和可维护性。
总结来说,静态代理就像手动找朋友帮忙监督员工,虽然实现了功能,但扩展困难、管理分散。而动态代理就像雇佣专业团队,自动、集中且灵活地处理代理任务,更适合应对复杂多变的需求。
要实现JDK动态代理,需要遵循以下步骤:
首先确保您的目标对象实现了某个接口(如本例中的Calculator接口)。这是JDK动态代理的基本要求,因为JDK动态代理只能针对接口进行代理。
创建一个实现了java.lang.reflect.InvocationHandler接口的类,它负责处理代理对象上所有方法的调用。在这个类中实现invoke()方法,该方法接收三个参数:
Object proxy: 代理对象本身。
Method method: 被调用的方法对象。
Object[ ] args: 调用方法时传入的实际参数数组。
在invoke()方法中,您可以添加额外的日志记录或其他横切关注点逻辑,然后调用目标对象的实际方法。
使用java.lang.reflect.Proxy类的newProxyInstance()静态方法创建代理对象。此方法需要以下三个参数:
ClassLoader loader: 目标对象的类加载器。
Class<?>[] interfaces: 目标对象所实现的接口数组。
InvocationHandler h: 实现了InvocationHandler接口的对象,即第二步中创建的类实例。
生产代理对象的工厂类:
- package com.sakurapaid.spring6.aop.demo;
-
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.util.Arrays;
-
- /**
- * 动态代理工厂类,用于创建目标对象的动态代理实例。
- */
- public class ProxyFactory {
- private Object target;
-
- /**
- * 构造函数,初始化目标对象。
- * @param target 目标对象,将为此对象创建动态代理。
- */
- public ProxyFactory(Object target) {
- this.target = target;
- }
-
- /**
- * 获取目标对象的动态代理实例。
- * @return 返回代理对象,该对象实现了目标对象的所有接口。
- */
- public Object getProxy(){
- // 获取目标对象的类加载器和实现的接口数组
- ClassLoader classLoader = target.getClass().getClassLoader(); // 获取目标对象的类加载器
- Class<?>[] interfaces = target.getClass().getInterfaces(); // 获取目标对象的实现的接口数组
-
- // 设置代理对象的行为,即在调用任何方法时执行的逻辑
- InvocationHandler invocationHandler = new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 在方法执行前后打印日志
- Object result = null;
- try {
- System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
- result = method.invoke(target, args); // 调用目标对象的方法
- System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
- } catch (Exception e) {
- e.printStackTrace();
- System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
- } finally {
- System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
- }
- return result;
- }
- };
-
- // 创建并返回代理实例
- return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
- }
- }

测试输出
- package com.sakurapaid.spring6.aop.demo;
-
- import org.junit.jupiter.api.Test;
-
- /**
- * 计算器测试类,用于测试动态代理功能。
- */
- public class CaculatorTest {
-
- /**
- * 测试使用动态代理进行方法拦截。
- * 该测试方法不接受参数,也不返回任何值。
- * 主要流程是创建一个代理工厂,针对CalculatorLogImpl实例生成一个动态代理对象,
- * 然后通过该代理对象调用add方法,以验证动态代理的拦截功能是否正常。
- */
- @Test
- public void testDynamicProxy(){
- // 创建代理工厂,并设置目标对象为CalculatorLogImpl的实例
- ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
- // 通过代理工厂获取代理对象
- Calculator proxy = (Calculator) factory.getProxy();
- // 使用代理对象调用add方法,验证动态代理是否生效
- proxy.add(1,2);
- }
- }

AOP是什么?
想象你开了一家餐厅,餐厅里有很多事情要做:厨师负责烹饪美食(这是主业务,就像程序中的核心业务逻辑),服务员接待客人、点菜、上菜(这也是业务的一部分),还有清洁工打扫卫生、收银员结账等。除此之外,还有一些贯穿整个运营过程的“边缘任务”,比如:
这些“边缘任务”(我们称它们为“切面”)虽然不是直接做菜或服务客人,但对餐厅的正常运营至关重要。而且,它们会涉及到餐厅的各个环节,不是只针对某一特定角色或环节。
传统的做法
以前,你可能让厨师、服务员、清洁工等人在忙完自己的本职工作后,额外花时间去记账、消毒、提醒采购。这样做的问题是:
AOP的做法
AOP就像是请了一个全能助手,专门负责这些“边缘任务”。助手站在一旁观察餐厅的运营情况,按照设定好的规则自动执行:
这样做的好处:
映射到编程领域
在软件开发中,AOP就是那个全能助手。它能在程序运行时,自动在合适的地方插入额外的代码(比如记录日志、开启事务、权限检查等),而不用修改原来的业务代码。这样:
总结来说,AOP是一种编程思想,它让程序能够自动在执行主业务逻辑的同时,处理好那些与业务逻辑交织但又相对独立的“边缘任务”,使得代码更清晰、更易维护,同时也提高了开发效率。在Spring框架中,AOP提供了具体的工具和技术实现这一思想。
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
比喻: 想象你经营一家餐厅,每天有许多日常事务要处理。其中有一些事务是贯穿整个餐厅运营的,就像一根根“红线”贯穿于各个部门之间。例如:
这些贯穿于餐厅各个角落、各部门都需处理的共同问题,就像“红线”一样横跨各个业务模块,我们称之为“横切关注点”。
解释: 在软件开发中,横切关注点指的是那些在不同业务模块中反复出现、与业务逻辑本身关系不大,但又不可或缺的处理逻辑。比如用户验证、日志记录、事务管理、数据缓存等。它们并非某一个具体功能的核心部分,而是与整个系统的行为规范、运行环境、管理需求等相关。
比喻: 假设你想提升餐厅的服务质量,于是决定给餐厅添加一些“增强功能”。比如:
这些额外的互动环节就像给餐厅原有的服务流程加上了“增强功能”,让顾客体验更佳。
解释: 在AOP中,通知(增强)就是指那些在目标方法执行前后(或过程中)插入的额外操作,用来实现横切关注点所需的功能。比如:
比喻: 为了让餐厅更好地运营,你专门设立了一个“服务质量提升小组”,这个小组负责策划并实施所有的“增强功能”,如欢迎词、感谢语、优惠提醒等。这个小组就是所有“增强功能”的集合体。
解释: 在AOP中,切面就是一个封装了多个通知(增强)的类,它定义了哪些地方需要执行哪些增强逻辑。切面就像一个“服务质量提升小组”,集中管理与特定横切关注点相关的所有通知。
比喻: 在餐厅里,厨师专心炒菜、服务员热情待客,他们是各自岗位上的“目标”,他们的主要任务是完成本职工作。
解释: 在AOP中,目标就是指那些被增强的对象或方法,即我们想要在其周围添加通知的原始业务逻辑。比如某个处理订单的类或方法,它们专注于处理订单的核心业务,是AOP要进行增强的“目标”。
比喻: 如果你聘请了一位餐厅经理,他并不直接炒菜或服务顾客,但他会指导厨师和服务员如何更好地工作,并在必要时介入处理一些特殊情况。这位经理就像是厨师和服务员的“代理”,他们对外展示的是经理协调下的综合服务能力。
解释: 在AOP中,代理就是指为目标对象生成的一个替代者,它在保留原对象功能的基础上,包含了切面中定义的通知逻辑。客户端代码实际上与代理对象交互,代理对象在调用目标方法时会适时执行相应的通知。
比喻: 在餐厅运营过程中,任何一个可以插入“增强功能”的时机都可以看作一个“连接点”。比如顾客刚坐下时、点完菜后、结账时、离开时等都是可以插入欢迎词、感谢语、优惠提醒等“增强功能”的“连接点”。
解释: 在AOP中,连接点是一个抽象概念,它表示在程序执行过程中能够插入通知的所有可能位置。具体来说,通常是某个方法的执行前、执行后、抛出异常时、正常返回后等时刻。连接点就像一张餐厅运营流程的时间线图,图上标注了所有可以插入“增强功能”的时间节点。
比喻: 如果你决定只在顾客入座时和结账时进行“增强功能”(欢迎词、感谢语),那么这两个特定的“连接点”就是你要瞄准的“切入点”。
解释: 在AOP中,切入点就是用来精确指定应该在哪些连接点上应用通知的表达式或规则。它就像一个过滤器,从众多连接点中选出真正需要插入通知的具体位置。比如,可能只选择所有以“save
”开头的方法作为切入点,对这些方法执行事务管理的增强。切入点就像一份详细的“增强功能”实施计划,指定了在哪些具体场景(方法调用)中启用哪些“增强功能”。
想象您经营一家咖啡馆,每天有很多顾客来点咖啡。在制作咖啡的过程中,除了实际的冲泡工作(主业务逻辑),还有一些附加的操作是每次都需要做的,比如:
这些附加的操作(记录订单、检查库存、清洗器具)就像是“横切关注点”,它们贯穿于咖啡冲泡流程的各个环节,但并非咖啡制作的核心逻辑。AOP就像是一个聪明的经理,他设计了一个系统,让这些附加操作能够自动完成,而不干扰咖啡师专心制作咖啡。
注解(Annotations)就像经理给咖啡师发了一份清单,上面标注了哪些步骤前后需要做什么。例如,经理可能用不同的标签(注解)来标记:
@BeforeMakeCoffee
:表示在开始冲泡前,要先检查库存和清洗器具。@AfterMakeCoffee
:表示咖啡制作完成后,要立即记录订单。咖啡师只要按照清单上的标注(注解)执行相应操作即可,不用关心具体细节。
在Java中,主要存在两种动态代理实现方式:
JDK动态代理和CGLIB(Code Generation Library)动态代理。
简单来说,JDK动态代理就像有两个对象(代理对象和目标对象)共同遵守一个“约定(接口),代理对象作为目标对象的“代理人”,在对外提供服务时,会先做一些额外的工作,然后才调用目标对象的实际方法。这里的“约定”就是接口,即目标对象必须实现一个或多个接口。JDK动态代理利用java.lang.reflect.Proxy
类和InvocationHandler
接口来创建代理对象。
具体过程如下:
com.sun.proxy
包下,类名如 $proxy1
),这个代理类实现了与目标类相同的接口。InvocationHandler
接口的invoke()
方法,该方法接收你的方法调用请求,并在此处添加你想要的额外逻辑(如前置处理、后置处理等)。最后,invoke()
方法再调用目标对象的实际方法来完成原始功能。总结:JDK动态代理适用于那些目标类已经实现了接口的情况。代理对象通过实现相同的接口并与目标对象形成“兄弟关系”,在对外提供服务时扮演中间人的角色,通过InvocationHandler
接口添加额外功能。
CGLIB动态代理则采取了不同的策略,它不是基于接口而是通过继承被代理的目标类来创建代理对象。这意味着即使目标类没有实现任何接口,只要它是非final
的,CGLIB也可以为其创建代理。
具体过程如下:
总结:CGLIB动态代理适用于那些目标类没有实现接口或者你不希望依赖接口的情况。代理对象通过继承目标类成为其“子类”,并在方法调用时通过方法拦截器插入额外功能,实现对目标类行为的扩展或修改。
综上所述,JDK动态代理基于接口,通过实现相同接口的代理类来提供代理功能;而CGLIB动态代理则是通过继承目标类并修改其实现,达到无接口情况下的代理目的。两种方式各有适用场景,根据实际需求选择合适的代理技术。在Spring框架中,AOP(面向切面编程)会根据目标类是否实现接口自动选择使用JDK动态代理还是CGLIB动态代理。
AspectJ 是一种实现了 AOP(面向切面编程)思想的框架,尽管它的机制和之前提到的动态代理(如JDK动态代理和CGLIB动态代理)有所不同,但它们都服务于同一个目标:在不修改原始代码的情况下,对程序的某些横切关注点(如日志、事务、安全检查等)进行统一管理和模块化。
织入器(Weaver)是负责执行上述织入过程的组件,它解析切面定义,识别切点,并按照指定的连接点(Join point)将切面逻辑插入到目标类的方法调用、字段访问等合适的位置。
在 Spring 框架中,虽然它内置了基于动态代理的AOP(JDK、CGLIB)支持,但也可以选择与 AspectJ 集成,利用 AspectJ 的强大切面定义能力和更广泛的织入点支持来编写更复杂的切面。Spring 通过引入 @Aspect
注解和相关的配置,使得开发者可以使用类似Spring AOP的风格编写切面,但底层实际上是借助AspectJ的编译器或织入器来实现切面逻辑的织入。
总结:AspectJ 实现了AOP思想,它采用静态代理的方式,在编译阶段(或加载阶段)将代理逻辑“织入”到目标类的字节码中。这种方式提供了更强大的切面定义能力和更广泛的织入点支持,使得开发者能够在不影响原有业务代码结构的前提下,灵活、集中地管理横切关注点。Spring框架可以与AspectJ集成,借用其注解和强大的切面功能来增强Spring应用的AOP能力。
总结来说,基于注解的AOP就像是给咖啡师发了一份带有标注的操作清单,让他们在制作咖啡的同时自动完成附加任务。
Spring AOP可以根据需要灵活选用这些技术,实现系统的自动化管理。
操作步骤:
第1步:引入aop相关依赖
第2步:创建目标资源,接口、实现类
第3步:创建切面点,切入点、通知类型
在IOC所需依赖基础上再加入下面依赖即可:
- <dependencies>
- <!--spring context依赖-->
- <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>6.0.2</version>
- </dependency>
-
- <!--spring aop依赖-->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aop</artifactId>
- <version>6.0.2</version>
- </dependency>
- <!--spring aspects依赖-->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aspects</artifactId>
- <version>6.0.2</version>
- </dependency>
-
- <!--junit5测试-->
- <dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-api</artifactId>
- <version>5.3.1</version>
- </dependency>
-
- <!--log4j2的依赖-->
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-core</artifactId>
- <version>2.19.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-slf4j2-impl</artifactId>
- <version>2.19.0</version>
- </dependency>
- </dependencies>

接口:
- package com.sakurapaid.spring6.aop.annoxml;
-
- /**
- * 计算器接口,定义了基本的四则运算方法。
- */
- public interface Calculator {
-
- int add(int i, int j);
-
- int sub(int i, int j);
-
- int mul(int i, int j);
-
- int div(int i, int j);
- }
JDK动态代理适用于那些目标类已经实现了接口的情况。代理对象通过实现相同的接口并与目标对象形成“兄弟关系”,在对外提供服务时扮演中间人的角色,通过InvocationHandler
接口添加额外功能。
创建接口的实现类:
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.springframework.stereotype.Component;
-
- /**
- * 实现Calculator接口的计算类,提供基本的四则运算功能。
- */
- @Component // 将CalculatorImpl注册为Spring组件,交给Spring容器管理
- public class CalculatorImpl implements Calculator{
- @Override
- public int add(int i, int j) {
- int result = i + j;
- System.out.println("方法内部 result = " + result);
- return result;
- }
-
- @Override
- public int sub(int i, int j) {
- int result = i - j;
- System.out.println("方法内部 result = " + result);
- return result;
- }
-
- @Override
- public int mul(int i, int j) {
- int result = i * j;
- System.out.println("方法内部 result = " + result);
- return result;
- }
-
- @Override
- public int div(int i, int j) {
- int result = i / j;
- System.out.println("方法内部 result = " + result);
- return result;
- }
- }

创建切面类并配置
@对应注解 (value = "切入点表达式配置切入点")
具体切入点表达式怎么写,后面会讲到
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
- import java.util.Arrays;
-
- @Aspect //声明这是一个切面类
- @Component //将切面类注册为Spring组件
- public class LogAspect {
-
- /**
- * 方法执行前的通知。记录方法名和参数。
- *
- * @param joinPoint 切入点,用于获取方法名和参数等信息。
- */
- @Before(value = "execution(public int com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.add(..))")
- public void beforeMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName(); //获取方法名
- String args = Arrays.toString(joinPoint.getArgs()); //获取参数
- System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
- }
-
- }

测试输出
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.junit.jupiter.api.Test;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
-
- /**
- * 计算器测试类,用于测试动态代理功能。
- */
- public class CaculatorTest {
-
- @Test
- public void testAdd(){
- ApplicationContext ac = new ClassPathXmlApplicationContext("SpringAop.xml");
- Calculator calculator = ac.getBean(Calculator.class);
- calculator.add(1,2);
- }
- }

刚才在切面类只设置一个前置通知,这会全部通知都加上
配置切面类
根据切面表达式,我对所有的方法都进行了通知
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
- import java.util.Arrays;
-
- @Aspect //声明这是一个切面类
- @Component //将切面类注册为Spring组件
- public class LogAspect {
- @Before("execution(public int com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void beforeMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
- }
-
- @After("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void afterMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->后置通知,方法名:"+methodName);
- }
-
- @AfterReturning(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", returning = "result")
- public void afterReturningMethod(JoinPoint joinPoint, Object result){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
- }
-
- @AfterThrowing(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", throwing = "ex")
- public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
- }
-
- @Around("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public Object aroundMethod(ProceedingJoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- Object result = null;
- try {
- System.out.println("环绕通知-->目标对象方法执行之前");
- //目标对象(连接点)方法的执行
- result = joinPoint.proceed();
- System.out.println("环绕通知-->目标对象方法返回值之后");
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- System.out.println("环绕通知-->目标对象方法出现异常时");
- } finally {
- System.out.println("环绕通知-->目标对象方法执行完毕");
- }
- return result;
- }
- }

再重新测试输出
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.junit.jupiter.api.Test;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
-
- /**
- * 计算器测试类,用于测试动态代理功能。
- */
- public class CaculatorTest {
-
- @Test
- public void testAdd(){
- ApplicationContext ac = new ClassPathXmlApplicationContext("SpringAop.xml");
- Calculator calculator = ac.getBean(Calculator.class);
- System.out.println("1+2");
- calculator.add(1,2);
- System.out.println("-----------");
- System.out.println("1-2");
- calculator.sub(1,2);
- System.out.println("-----------");
- System.out.println("1*2");
- calculator.mul(1,2);
- System.out.println("-----------");
- System.out.println("1/2");
- calculator.div(1,2);
- }
- }

@Before
):这是一个在目标方法被调用前执行的通知。它可以用于设置上下文、验证参数、开启事务等预处理操作。@AfterReturning
):当目标方法成功执行并返回时触发。此通知可以用来执行清理工作、关闭资源、发布事件或基于方法返回值进行进一步处理。@AfterThrowing
):如果目标方法抛出异常,则会执行异常通知。它通常用于记录错误日志、回滚事务、通知监控系统等与异常处理相关的行为。@After
):无论目标方法是否正常完成(无论有无返回值、是否抛出异常),后置通知都会在方法执行完毕后执行。它适用于执行那些无论方法执行结果如何都需要做的清理工作,如释放通用资源、关闭数据库连接等。@Around
):环绕通知最为灵活,它能够完全控制目标方法的执行流程。它以try...catch...finally
结构包裹目标方法,允许在方法调用前后插入自定义行为,并且可以根据需要决定是否继续执行目标方法、何时执行以及如何处理返回值或抛出的异常。每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。通知方法在AOP(面向切面编程)中扮演着关键角色,它们负责在特定的程序执行点(如方法调用前、后、返回结果或抛出异常时)执行相应的横切逻辑。具体来说,常见的五种通知方法包括:
- @Before("execution(public int com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void beforeMethod(JoinPoint joinPoint){
- // 记录方法名和参数
- }
- @AfterReturning(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", returning = "result")
- public void afterReturningMethod(JoinPoint joinPoint, Object result){
- // 记录方法名和返回结果
- }
- @AfterThrowing(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", throwing = "ex")
- public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
- // 记录方法名和异常信息
- }
- @After("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void afterMethod(JoinPoint joinPoint){
- // 记录方法名
- }
- @Around("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public Object aroundMethod(ProceedingJoinPoint joinPoint){
- // 在方法执行前后插入自定义逻辑,控制方法执行
- }
综上所述,这五种通知方法分别对应了目标方法执行生命周期中的不同阶段,通过编写这些通知方法,我们可以将与业务逻辑无关的横切关注点(如日志、安全、事务等)从业务代码中剥离出来,实现代码的解耦和复用,提升系统的可维护性和扩展性。
针对每个通知方法中的传入参数的简要解释:
JoinPoint joinPoint
JoinPoint
: 这是AOP框架提供的一个接口,代表程序执行过程中的一个具体点(如方法调用)。在前置通知中,joinPoint
参数包含了关于当前被拦截方法的各种信息,如方法名、方法签名(包含参数类型和返回类型)、目标对象、方法参数值等。通过这个参数,您可以在通知方法中获取到目标方法的相关上下文,以便进行诸如日志记录、权限校验等操作。例如,您可以使用joinPoint.getSignature().getName()
获取方法名,或者joinPoint.getArgs()
获取方法参数值。JoinPoint joinPoint
, Object result
JoinPoint joinPoint
: 同前置通知中的joinPoint
,提供了关于目标方法的详细信息。Object result
: 这个参数代表目标方法成功执行后返回的结果。在返回通知中,您可以访问到这个结果对象,进行后续的数据校验、缓存更新、统计分析等操作。例如,如果目标方法返回一个计算结果,您可以通过result
变量获取并记录这个结果。JoinPoint joinPoint
, Throwable ex
JoinPoint joinPoint
: 同前置通知中的joinPoint
,提供了关于目标方法的详细信息。Throwable ex
: 这个参数代表目标方法执行过程中抛出的异常实例。在异常通知中,您可以访问到这个异常对象,记录异常信息、执行回滚操作、发送错误通知等。例如,您可以使用ex.getMessage()
获取异常消息,或者ex.printStackTrace()
打印异常堆栈跟踪。JoinPoint joinPoint
JoinPoint joinPoint
: 同前置通知中的joinPoint
,提供了关于目标方法的详细信息。在后置通知中,您可以使用这个参数进行资源释放、环境清理等与方法执行结果无关的收尾工作。ProceedingJoinPoint joinPoint
ProceedingJoinPoint joinPoint
: 它继承自JoinPoint
接口,除了包含普通JoinPoint
的所有信息外,还提供了额外的方法proceed()
,允许您控制目标方法的执行。在环绕通知中,您可以编写自定义逻辑包围joinPoint.proceed()
调用,实现对目标方法调用前后及过程中的干预。例如,您可以在proceed()
前后添加事务管理代码、性能监控逻辑或日志记录语句。总结:每个通知方法中的参数主要是为了提供目标方法的相关上下文信息(如方法名、参数、返回值、抛出的异常等)以及控制目标方法执行的能力(如环绕通知中的ProceedingJoinPoint
)。通过这些参数,您可以根据需要编写相应的横切逻辑,将关注点与业务代码分离,提高代码的可维护性和可扩展性。作为初学者,理解这些参数如何帮助您定位和操作目标方法是掌握AOP的关键。
代码示例就是下面这样的:
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- import java.util.Arrays;
-
- /**
- * 日志切面类,用于通过注解方式定义切面逻辑,包括方法执行前、后、返回结果后、抛出异常后的日志记录。
- */
- @Aspect //声明这是一个切面类
- @Component //将切面类注册为Spring组件
- public class LogAspect {
-
- /**
- * 方法执行前的通知。记录方法名和参数。
- *
- * @param joinPoint 切入点,用于获取方法名和参数等信息。
- */
- @Before("execution(public int com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void beforeMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
- }
-
- /**
- * 方法执行后的通知。仅记录方法名。
- *
- * @param joinPoint 切入点,用于获取方法名。
- */
- @After("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void afterMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->后置通知,方法名:"+methodName);
- }
-
- /**
- * 方法返回结果后的通知。记录方法名和返回结果。
- *
- * @param joinPoint 切入点,用于获取方法名。
- * @param result 方法的返回结果。
- */
- @AfterReturning(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", returning = "result")
- public void afterReturningMethod(JoinPoint joinPoint, Object result){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
- }
-
- /**
- * 方法抛出异常后的通知。记录方法名和异常信息。
- *
- * @param joinPoint 切入点,用于获取方法名。
- * @param ex 方法抛出的异常。
- */
- @AfterThrowing(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", throwing = "ex")
- public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
- }
-
- /**
- * 环绕通知。在方法执行前后都可以插入自定义逻辑,还可以控制是否执行方法本身。
- *
- * @param joinPoint 切入点,用于获取方法名、参数,以及执行方法本身。
- * @return 方法的返回结果。
- * @throws Throwable 如果执行方法过程中出现异常。
- */
- @Around("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public Object aroundMethod(ProceedingJoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- Object result = null;
- try {
- System.out.println("环绕通知-->目标对象方法执行之前");
- // 执行目标方法
- result = joinPoint.proceed();
- System.out.println("环绕通知-->目标对象方法返回值之后");
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- System.out.println("环绕通知-->目标对象方法出现异常时");
- } finally {
- System.out.println("环绕通知-->目标对象方法执行完毕");
- }
- return result;
- }
-
- }

在Spring的配置文件中配置:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop.xsd">
- <!--
- 基于注解的AOP的实现:
- 1、将目标对象和切面交给IOC容器管理(注解+扫描)
- 2、开启AspectJ的自动代理,为目标对象自动生成代理
- 3、将切面类通过注解@Aspect标识
- -->
-
- <!-- 通过扫描指定包下所有组件并加入IOC容器,来实现对注解的自动识别和处理 -->
- <context:component-scan base-package="com.sakurapaid.spring6.aop.annoxml">
-
- </context:component-scan>
-
- <!-- 开启AspectJ的自动代理功能,它会为目标对象自动生成代理,以实现切面的织入 -->
- <aop:aspectj-autoproxy />
- </beans>

Spring版本5.3.x以前:
@Before
)@After
)@AfterReturning
或@AfterThrowing
),根据目标方法实际执行情况选择执行其中一个Spring版本5.3.x以后:
@Before
)@AfterReturning
或@AfterThrowing
),根据目标方法实际执行情况选择执行其中一个@After
)切入点表达式是用来描述你希望在何处应用AOP(面向切面编程)的特定行为(如日志记录、事务管理等)。它是AOP框架识别和定位要增强的方法的一种规则。以下是对各部分语法元素的简单解释:
*
表示任何权限修饰符(如public
、private
、protected
、默认无修饰符)均可。*
表示返回值类型不受限制,可以是任何类型。int
,则需同时指定权限修饰符。*.Hello
表示匹配直接在指定包下的类,如com.Hello
,但不匹配子包内的类,如com.atguigu.Hello
。*..Hello
表示匹配任何包层级下的Hello
类,不论包的深度如何。*
表示类名可以是任意名称。*Service
表示匹配所有类名以Service
结尾的类或接口,如MyService
、UserService
。*
表示方法名可以是任意名称。*Operation
表示匹配所有方法名以Operation
结尾的方法,如doOperation
、performOperation
。(..)
表示方法可以接受任意数量、任意类型的参数。(int, ..)
表示方法的第一个参数必须是int
类型,后面可以有任意数量、任意类型的其他参数。int
与实际方法中使用Integer
是不匹配的。如果你指定了int
作为参数类型,那么只有接收int
参数的方法才会被匹配。execution(public int ..Service.*(.., int))
是正确的,而只写execution(int ..Service.(.., int))
则是错误的。综合示例:
execution(* com.example.service.*Service.*Operation(..))
这个表达式表示:
com.example.service
包及其所有子包下的所有类(类名以Service
结尾),Operation
结尾的方法,总之,切入点表达式通过组合这些元素来精确地定位到需要增强的方法,使你能在指定的代码执行点插入额外的行为,实现业务逻辑与横切关注点的分离。
在AOP(面向切面编程)中,当我们在编写切面(Aspect)时,可能会发现针对同一类或一组类的方法,我们需要添加多种类型的切面通知(如前置通知、后置通知、返回通知、异常通知等),并且这些通知往往使用的是相同的切入点表达式。为了减少代码冗余,提高可维护性,我们可以采用重用切入点表达式的方法。
比如下面这个类中的切面表达式(我从上面扒下来的)
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- import java.util.Arrays;
-
- /**
- * 日志切面类,用于通过注解方式定义切面逻辑,包括方法执行前、后、返回结果后、抛出异常后的日志记录。
- */
- @Aspect
- @Component
- public class LogAspect {
-
- /**
- * 方法执行前的通知。记录方法名和参数。
- *
- * @param joinPoint 切入点,用于获取方法名和参数等信息。
- */
- @Before("execution(public int com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void beforeMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
- }
-
- /**
- * 方法执行后的通知。仅记录方法名。
- *
- * @param joinPoint 切入点,用于获取方法名。
- */
- @After("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void afterMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->后置通知,方法名:"+methodName);
- }
-
- /**
- * 方法返回结果后的通知。记录方法名和返回结果。
- *
- * @param joinPoint 切入点,用于获取方法名。
- * @param result 方法的返回结果。
- */
- @AfterReturning(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", returning = "result")
- public void afterReturningMethod(JoinPoint joinPoint, Object result){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
- }
-
- /**
- * 方法抛出异常后的通知。记录方法名和异常信息。
- *
- * @param joinPoint 切入点,用于获取方法名。
- * @param ex 方法抛出的异常。
- */
- @AfterThrowing(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", throwing = "ex")
- public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
- }
-
- /**
- * 环绕通知。在方法执行前后都可以插入自定义逻辑,还可以控制是否执行方法本身。
- *
- * @param joinPoint 切入点,用于获取方法名、参数,以及执行方法本身。
- * @return 方法的返回结果。
- * @throws Throwable 如果执行方法过程中出现异常。
- */
- @Around("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public Object aroundMethod(ProceedingJoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- Object result = null;
- try {
- System.out.println("环绕通知-->目标对象方法执行之前");
- // 执行目标方法
- result = joinPoint.proceed();
- System.out.println("环绕通知-->目标对象方法返回值之后");
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- System.out.println("环绕通知-->目标对象方法出现异常时");
- } finally {
- System.out.println("环绕通知-->目标对象方法执行完毕");
- }
- return result;
- }
- }

它包含了多个通知方法,分别用于在CalculatorImpl类中各个方法的执行前、后、返回结果后和抛出异常后进行日志记录。注意到这些通知方法的@Before、@After、@AfterReturning、@AfterThrowing和@Around注解中,切入点表达式都是重复的:
为了解决上述重复问题,我们可以按照如下步骤进行重用切入点表达式的重构:
① 声明一个公共的切入点方法:
- @Pointcut("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void calculatorMethods() {
-
- }
在这个方法上使用@Pointcut注解,将重复的切入点表达式定义为一个方法(这里命名为calculatorMethods),该方法本身不包含任何实现代码。
② 在各个通知方法中引用这个公共切入点:
然后,在各个通知方法的注解中,不再直接写切入点表达式,而是引用刚刚声明的calculatorMethods方法名。例如:
- @Before("calculatorMethods()")
- public void beforeMethod(JoinPoint joinPoint) {...}
-
- @After("calculatorMethods()")
- public void afterMethod(JoinPoint joinPoint) {...}
-
- @AfterReturning(value = "calculatorMethods()", returning = "result")
- public void afterReturningMethod(JoinPoint joinPoint, Object result) {...}
-
- @AfterThrowing(value = "calculatorMethods()", throwing = "ex")
- public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {...}
-
- @Around("calculatorMethods()")
- public Object aroundMethod(ProceedingJoinPoint joinPoint) {...}
在不同切面的重用切入表达式的使用
上述方式是在相同的切面进行重用切入表达式,在不同切面中使用已声明的切入点表达式,实际上是与在同一切面中使用的原理相同,只是切入点方法所在的类不同。当需要在多个切面类中共享同一个切入点表达式时,可以将这个公共切入点方法放在一个单独的类中,然后在各个切面类中通过全限定名引用这个公共切入点方法。
假设我们有一个名为CommonPointcuts
的类,专门用来存放公共的切入点方法:
- package com.sakurapaid.spring6.aop.common;
-
- import org.aspectj.lang.annotation.Pointcut;
-
- public class CommonPointcuts {
-
- @Pointcut("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void calculatorMethods() {}
- }
现在,如果我们有一个新的切面类AnotherLogAspect
,也需要对CalculatorImpl
类中的方法应用相同的切面逻辑,可以这样引用公共切入点:
- package com.sakurapaid.spring6.aop.another;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
-
- import com.sakurapaid.spring6.aop.common.CommonPointcuts;
-
- @Aspect
- public class AnotherLogAspect {
-
- @Before("com.sakurapaid.spring6.aop.common.CommonPointcuts.calculatorMethods()")
- public void anotherBeforeMethod(JoinPoint joinPoint) {
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- System.out.println("Another Logger-->前置通知,方法名:" + methodName + ",参数:" + args);
- }
-
- // 其他通知方法...
- }

在这个AnotherLogAspect
类中,@Before
注解中的切入点表达式不再是直接写的,而是引用了CommonPointcuts
类中声明的calculatorMethods
方法。这样,即使在不同的切面类中,我们依然可以通过全限定名引用同一个已声明的切入点,实现了跨切面的切入点表达式重用。
总结来说,要在不同切面中重用切入点表达式,只需将公共切入点方法放在一个独立的类中,然后在各个切面类中通过全限定名引用这个公共切入点方法即可。这样既能保持代码的简洁性和一致性,又便于维护。
在面向切面编程(AOP)中,当针对相同的目标方法应用了多个切面时,这些切面可能会按照特定的顺序执行其通知(如前置通知、后置通知等)。这个顺序就是所谓的“切面优先级”。理解切面优先级对于控制切面行为的嵌套顺序至关重要,特别是在涉及多个切面协同工作或有特定执行顺序需求的场景。
切面优先级的基本概念:
控制切面优先级:
Spring AOP提供了@Order
注解来显式指定切面的优先级。这个注解可以放在切面类上,其值是一个整数,数值越小,优先级越高。
示例说明:
假设我们有两个切面类,LoggingAspect
负责日志记录,TransactionAspect
负责事务管理,它们都需要应用于某个服务方法。由于事务管理通常要求在日志记录之前启动,而在日志记录之后提交/回滚,因此TransactionAspect
的优先级应高于LoggingAspect
。
- // 优先级较高,负责事务管理
- @Aspect
- @Order(1) // 数值较小,优先级高
- public class TransactionAspect {
- // 事务相关的通知方法...
- }
-
- // 优先级较低,负责日志记录
- @Aspect
- @Order(2) // 数值较大,优先级低
- public class LoggingAspect {
- // 日志记录相关的通知方法...
- }
在上述例子中,TransactionAspect
的优先级设置为1,LoggingAspect
的优先级设置为2。因此,在目标方法执行时,会先执行TransactionAspect
的前置通知(如开启事务),接着执行目标方法,然后执行LoggingAspect
的后置通知(如记录日志),最后执行TransactionAspect
的后置通知(如提交/回滚事务)。这种顺序确保了事务管理逻辑在外层,日志记录逻辑在内层,符合预期的业务需求。
总结:
切面的优先级决定了多个切面作用于相同目标方法时,其通知执行的内外嵌套顺序。通过使用@Order
注解并指定一个整数值,可以轻松控制切面的优先级。数值越小,优先级越高,切面通知越“靠外”执行;数值越大,优先级越低,切面通知越“靠内”执行。合理设置切面优先级有助于确保切面行为按预期顺序执行,满足特定的业务需求或技术约束。
参考基于注解的AOP环境
接口:
- package com.sakurapaid.spring6.aop.annoxml;
-
- /**
- * 计算器接口,定义了基本的四则运算方法。
- */
- public interface Calculator {
-
- int add(int i, int j);
-
- int sub(int i, int j);
-
- int mul(int i, int j);
-
- int div(int i, int j);
- }
创建接口的实现类:
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.springframework.stereotype.Component;
-
- /**
- * 实现Calculator接口的计算类,提供基本的四则运算功能。
- */
- @Component // 将CalculatorImpl注册为Spring组件,交给Spring容器管理
- public class CalculatorImpl implements Calculator{
- @Override
- public int add(int i, int j) {
- int result = i + j;
- System.out.println("方法内部 result = " + result);
- return result;
- }
-
- @Override
- public int sub(int i, int j) {
- int result = i - j;
- System.out.println("方法内部 result = " + result);
- return result;
- }
-
- @Override
- public int mul(int i, int j) {
- int result = i * j;
- System.out.println("方法内部 result = " + result);
- return result;
- }
-
- @Override
- public int div(int i, int j) {
- int result = i / j;
- System.out.println("方法内部 result = " + result);
- return result;
- }
- }

配置切面类
因为是基于XML配置的,所以这里是不写@Aspect注解
并且把每个方法上面的注解都去掉
- package com.sakurapaid.spring6.aop.annoxml;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
- import java.util.Arrays;
-
- //@Aspect //声明这是一个切面类
- @Component //将切面类注册为Spring组件
- public class LogAspect {
- // @Before("execution(public int com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void beforeMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
- }
-
- // @After("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public void afterMethod(JoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->后置通知,方法名:"+methodName);
- }
-
- // @AfterReturning(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", returning = "result")
- public void afterReturningMethod(JoinPoint joinPoint, Object result){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
- }
-
- // @AfterThrowing(value = "execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))", throwing = "ex")
- public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
- }
-
- // @Around("execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))")
- public Object aroundMethod(ProceedingJoinPoint joinPoint){
- String methodName = joinPoint.getSignature().getName();
- String args = Arrays.toString(joinPoint.getArgs());
- Object result = null;
- try {
- System.out.println("环绕通知-->目标对象方法执行之前");
- //目标对象(连接点)方法的执行
- result = joinPoint.proceed();
- System.out.println("环绕通知-->目标对象方法返回值之后");
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- System.out.println("环绕通知-->目标对象方法出现异常时");
- } finally {
- System.out.println("环绕通知-->目标对象方法执行完毕");
- }
- return result;
- }
- }

配置spring文件
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop.xsd">
-
-
- <!-- 扫描指定包下所有组件并加入IOC容器,实现对注解的自动识别和处理 -->
- <context:component-scan base-package="com.sakurapaid.spring6.aop.annoxml"/>
-
- <!-- 开启AspectJ的自动代理功能,自动为目标对象生成代理以实现切面的织入 -->
- <aop:aspectj-autoproxy/>
-
- <aop:config>
- <!-- 配置切面,包括切面类、切点、通知等 -->
- <aop:aspect ref="logAspect">
- <!-- 定义切点,这里指定了要拦截的方法执行 -->
- <aop:pointcut id="pointCut"
- expression="execution(* com.sakurapaid.spring6.aop.annoxml.CalculatorImpl.*(..))"/>
- <!-- 前置通知,在目标方法执行前执行 -->
- <aop:before method="beforeMethod" pointcut-ref="pointCut"/>
- <!-- 后置通知,在目标方法执行后执行,无论方法执行是否成功 -->
- <aop:after method="afterMethod" pointcut-ref="pointCut"/>
- <!-- 返回后通知,在目标方法返回结果后执行 -->
- <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut"/>
- <!-- 异常抛出后通知,在目标方法抛出异常后执行 -->
- <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"/>
- <!-- 环绕通知,可以在方法执行的任何时刻干预方法的执行 -->
- <aop:around method="aroundMethod" pointcut-ref="pointCut"/>
- </aop:aspect>
- </aop:config>
-
- </beans>

实现的效果是一样的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。