搭建子模块——spring6-aop
一、场景模拟
(1)声明接口
- 接口代码:
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); }
- 如图:
(2)创建实现类
- 实现类代码:
package com.atguigu.spring6.aop.example; 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; } }
- 如图:
(3)创建带日志功能的实现类
- 实现类代码:
package com.atguigu.spring6.aop.example; public class CalculatorLogImpl implements Calculator{ @Override 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 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 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 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; } }
- 可以发现,在这个类的方法中,我们自己写的日志功能和核心代码混合在一起,这样很不好。我们要把日志功能和核心代码分离开来
(4)提出问题
- 现有代码缺陷:针对带有日志功能的实现类,我们发现有如下缺陷:
- 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
- 附加的日志功能分散在各个业务功能方法中,不利于统一维护
- 解决思路:解决这两个问题的核心就是解耦。我们需要把附加功能从业务功能的代码中抽取出来
- 困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决,所以需要引入新的技术
二、代理模式
(1)概念
- 介绍:代理模式是二十三种设计模式中的一种,属于结构型模式。它的作用就是提供一个代理类,让我们在调用目标方法的时候,不再是对目标方法进行直接调用,而是通过代理类来间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起,也有利于统一维护
- 使用代理前:
- 使用代理后:
- 相关术语:
- 将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法
- 目标:被代理“套用”了非核心逻辑代码的类、对象、方法
(2)静态代理
- 创建静态代理类:
package com.atguigu.spring6.aop.example; public class CalculatorStaticProxy implements Calculator{ private Calculator calculator; public CalculatorStaticProxy(Calculator calculator) { this.calculator = calculator; } @Override public int add(int i, int j) { //输出日志 System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); int addResult = calculator.add(i, j); //输出日志 System.out.println("[日志] add 方法结束了,结果是:" + addResult); return addResult; } @Override public int sub(int i, int j) { //输出日志 System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j); int subResult = calculator.sub(i, j); //输出日志 System.out.println("[日志] sub 方法结束了,结果是:" + subResult); return subResult; } @Override public int mul(int i, int j) { //输出日志 System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j); int mulResult = calculator.mul(i, j); //输出日志 System.out.println("[日志] mul 方法结束了,结果是:" + mulResult); return mulResult; } @Override public int div(int i, int j) { //输出日志 System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j); int divResult = calculator.div(i, j); //输出日志 System.out.println("[日志] div 方法结束了,结果是:" + divResult); return divResult; } }
- 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其它地方也需要附加日志,那还得再声明多个静态代理类,那就产生了大量重复的代码。日志功能还是分散的,没有统一管理
- 再提出进一步的需求:将日志功能都集中到一个代理类,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了
(3)动态代理
- 如图:
- 首先我们创建了一个代理类ProxyFactory,(1)代理类里有一个私有属性:目标对象(2)代理类里有一个有参构造器(代码可以理解成即将根据目标对象生成代理)(3)代理类里有一个方法getProxy用于生成代理
- getProxy方法的返回类型是Object,因为Object是祖宗类,所以我们可以强制类型转换,把代理的类型转为目标对象的类型,然后代理就可以调用目标对象所属类中的方法了
- 当我们用代理去调用目标对象所属类中的方法时,实际上是调用了我们在getProxy方法中重写的invoke方法(个人理解)
- invoke方法中的三个参数(Object proxy, Method method, Object[] args)。proxy应该就是我们的代理,method就是代理调用的目标对象所属类中的方法,args是给该方法传入的参数
(4)测试
- 代理类的代码:
package com.atguigu.spring6.aop.example; 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; public ProxyFactory(Object target) { this.target = target; } //返回代理对象 public Object getProxy(){ //有三个形参 //第一个形参ClassLoader是类加载器 //第二个形参Class[] interfaces是目标对象实现的所有接口 //第三个形参InvocationHandler是设置代理对象实现目标对象方法的过程 ClassLoader classLoader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler(){ //第一个参数:代理对象 //第二个参数:需要重写目标对象的方法 //第三个参数:method方法里面的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args)); //调用目标的方法 Object result = method.invoke(target, args); System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result); return result; } }; return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); } }
- 测试类的代码:
package com.atguigu.spring6.aop.example; public class TestCal { public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl()); //创建代理对象(动态) Calculator proxy = (Calculator)proxyFactory.getProxy(); proxy.add(1,2); } }
- 测试效果:
三、AOP概念及相关术语
(1)概念
- AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善
- 它以预编译的方式和运行期动态代理方式实现。在不修改源码的情况下,给程序动态统一添加额外功能的一种技术
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
(2)相关术语
3.2.1横切关注点
- 分散在各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点
- 从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强
- 这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点
3.2.2通知(增强)
- 通知(增强):通俗地说,就是你想要增强的功能(即增加要执行的方法),比如:安全、事务、日志等
- 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫做通知
- 前置通知:在被代理的目标方法之前执行的方法
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置(怎么围绕整个被代理的目标方法呢?通过ProceedingJoinPoint类型的连接点,调用proceed方法)
- 图示:
- 注意:后置通知不管被代理的目标方法有没有成功执行,都会执行!!!
3.2.3切面
- 切面类:封装通知(即方法)的类
- 图示:
3.2.4目标
- 被代理的目标对象
3.2.5代理
- 向目标对象应用通知之后创建的代理对象
3.2.6连接点
- 把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点
- 通俗说,就是Spring允许你用通知的地方
- 图示:
3.2.7切入点
- 定位连接点的方式
- 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)
- 如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句
- Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法
- 切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件
(3)作用
- 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性
- 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了
四、基于注解的AOP
(1)技术说明
- 图示:
- 动态代理分为JDK动态代理和cglib动态代理
- 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
-
JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
-
cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
-
动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)
-
cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口
-
AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解
(2)准备工作
- 添加依赖(在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.atguigu.spring6.aop.example; 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.atguigu.spring6.aop.example; import org.springframework.stereotype.Component; @Component 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; } }
(3)创建切面类并配置
- 在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 "> <context:component-scan base-package="com.atguigu.spring6.aop"></context:component-scan> <aop:aspectj-autoproxy /> </beans>
- 接口代码和实现类代码:
package com.atguigu.spring6.aop.annoaop; 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.atguigu.spring6.aop.annoaop; import org.springframework.stereotype.Component; @Component public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i+j; System.out.println("方法内部 result = " + result); //int a = 1/0; 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; } }
- 切面类代码:
package com.atguigu.spring6.aop.annoaop; 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 { //设置切入点和通知类型 //通知类型:前置通知/返回通知/异常通知/后置通知/环绕通知 //前置通知@Before(value="切入点表达式配置切入点") @Before(value="execution(public int com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("Logger-->前置通知,方法名称:"+methodName+",参数"+ Arrays.toString(args)); } //后置通知@After @After(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("Logger-->后置通知,方法名称:"+methodName+",参数"+ Arrays.toString(args)); } //返回通知@afterReturningMethod @AfterReturning(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))",returning = "result") public void afterReturningMethod(JoinPoint joinPoint,Object result){ String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("Logger-->返回通知,方法名称:"+methodName+",参数"+ Arrays.toString(args)+",返回值"+result); } //异常通知@AfterThrowing 获取到目标方法的异常信息 //目标方法出现异常,这个通知执行 @AfterThrowing(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))",throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){ String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("Logger-->异常通知,方法名称:"+methodName+",参数"+ Arrays.toString(args)+",异常信息"+ex); } //环绕通知@Around() @Around(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); String argString = Arrays.toString(args); Object result = null; try{ System.out.println("环绕通知==目标方法之前执行"); result = joinPoint.proceed(); System.out.println("环绕通知==目标方法返回值之后"); }catch (Throwable throwable){ System.out.println("环绕通知==目标方法出现异常之后执行"); }finally { System.out.println("环绕通知==目标方法执行完毕执行"); } return result; } }
- 测试代码:
package com.atguigu.spring6.aop.annoaop; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestAop { @Test public void testAdd(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Calculator calculator = context.getBean(Calculator.class); //我们虽然是在CalculatorImpl上加的@Component注解,但是该类有接口 calculator.add(2,3); } }
- 也就是说,我们写了一个接口Calculator,写了一个接口的实现类CalculatorImpl,然后在这个实现类上面加了@Component注解,用于生成该类的一个对象。我们还写了一个切面类LogAspect,并在该切面类上用了@Component注解。当我们在测试类中使用CalculatorImpl的bean,并调用这个bean的方法时,还会执行通知
- XML中对通知的配置顺序可以决定通知的执行顺序!!!
(4)各种通知
- 前置通知:使用@Before注解标识,在被代理的目标方法前执行
- 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
- 各种通知的执行顺序:
- 这些通知可以用一个JoinPoint类型的参数。joinPoint.getSignature().getName();可以获得切入点的方法签名的方法名。joinPoint.getArgs();可以获得我们执行被代理的目标方法(切入点)时,给它传的参数,以一个数组形式返回。为什么在前置通知里也可以获得这些呢?因为我们先调用切入点,然后才会执行它。执行顺序:前置通知→目标操作→返回通知/异常通知→后置通知
- 在@AfterReturning()的括号里面,除了可以写value,还可以写returning。因为返回通知是在目标操作成功执行完以后才执行的,因此可以用returning获得目标操作的返回值。但是如果在后置通知的形参里要用到这个返回值,那么给返回值取的形参名和returing后面跟的要保持一致
- 在@AfterThrowing()的括号里面,除了可以写value,还可以写throwing。因为异常通知是在目标操作执行后才执行的,因此可以用throwing获取目标操作的异常信息。但是如果在异常通知的形参里要用到这个异常信息,那么给异常信息取的形参名和throwing后面跟的要保持一致
- 切入点我们可以理解成要被加上前置通知/返回通知/异常通知/后置通知/环绕通知的方法
(5)切入点表达式语法
- 图示:
- 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限(*同时覆盖权限修饰符和方法返回值)
- 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的(例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello )
- 在包名的部分,使用“*..”表示包名任意、包的层次深度任意
- 在类名的部分,类名部分整体用*号代替,表示类名任意
- 在类名的部分,可以使用*号代替类名的一部分(例如:例如:*Service匹配所有名称以Service结尾的类或接口 )
- 在方法名部分,可以使用*号表示方法名任意
- 在方法名部分,可以使用*号代替方法名的一部分(例如:*Operation匹配所有方法名以Operation结尾的方法)
- 在方法参数列表部分,使用(..)表示参数列表任意
- 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
- 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的(切入点表达式中使用 int 和实际方法中 Integer 是不匹配的)
- 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符(权限修饰符和返回值类型必须同时出现,要么同时被代替)
- 其实就是,execution(方法签名),只是这个方法签名的方法名部分,必须是包名+类名+方法名
(6)重用切入点表达式
- 可以发现,在上述代码中,这几个通知的切入点表达式有重复。其实我们可以把切入点表达式只写一次,然后重用
- 怎么重用切入点表达式?
//重用切入点表达式 @Pointcut(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))") public void pointCut(){}
- 然后把这几个通知的注解的value="execution(...)"换成value="pointCut()"。这个方法不一定要叫pointCut
- 如果有多个不同的切面(切面类也叫切面),那就要这么写。就是在pointCut()前面加上包名+切面类的类名
(7)获取通知的相关信息
- 获取连接点(JoinPoint)信息可以在通知的参数位置设置JoinPoint类型的形参
- 连接点可以理解为是通知和目标操作的连接。可以通过连接点,在通知中得到目标操作的相关信息
- @AfterReturning中的属性returning,用来将通知的某个形参,接收目标方法的返回值
- @AfterThrowing中的属性throwing,用来将通知的某个形参,接收目标方法的异常
(8)环绕通知
- 环绕通知的代码:
//环绕通知@Around() @Around(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); String argString = Arrays.toString(args); Object result = null; try{ System.out.println("环绕通知==目标方法之前执行"); result = joinPoint.proceed(); System.out.println("环绕通知==目标方法返回值之后"); }catch (Throwable throwable){ System.out.println("环绕通知==目标方法出现异常之后执行"); }finally { System.out.println("环绕通知==目标方法执行完毕执行"); } return result; }
- 注意:在前置通知、返回通知、异常通知、后置通知中使用的连接点都是JoinPoint类型。但是在环绕通知中,如果想调用目标操作,那么连接点就必须是ProceedingJoinPoint
(9)切面的优先级
- 相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序
- 优先级高的切面:外面
- 优先级低的切面:里面
- 使用@Order注解可以控制切面的优先级
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
- 图示:
五、基于XML的AOP
(1)准备工作
- 接口代码:
package com.atguigu.spring6.aop.xmlaop; 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.atguigu.spring6.aop.xmlaop; import org.springframework.stereotype.Component; @Component public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i+j; System.out.println("方法内部 result = " + result); //int a = 1/0; 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 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"> <!--开启组件扫描--> <context:component-scan base-package="com.atguigu.spring6.aop.xmlaop"></context:component-scan> <!--配置aop五种通知类型--> <aop:config> <!--配置切面类--> <aop:aspect ref="logAspect"> <!--配置切入点--> <aop:pointcut id="pointcut" expression="execution(* com.atguigu.spring6.aop.xmlaop.CalculatorImpl.*(..))"/> <!--配置五种通知类型--> <!--前置通知--> <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before> <!--后置通知--> <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after> <!--返回通知--> <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning> <!--异常通知--> <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointcut"></aop:after-throwing> <!--环绕通知--> <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around> </aop:aspect> </aop:config> </beans>
- 测试类代码:
package com.atguigu.spring6.aop.xmlaop; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestAop { @Test public void testAdd(){ ApplicationContext context = new ClassPathXmlApplicationContext("beanaop.xml"); Calculator calculator = context.getBean(Calculator.class); //我们虽然是在CalculatorImpl上加的@Component注解,但是该类有接口 calculator.add(2,3); } }
(2)实现
- 测试效果: