Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
【摘要】 Spring 第五节 AOP——切入点表达式 万字详解!
目录
2.1 通过@AfterReturning获取方法的返回结果
一、前言
- 第五节内容,up主要和大家分享一下Spring AOP方面的内容;包括通知类型,切入表达式 和 基于XML配置AOP。
- 注意事项——①代码中的注释也很重要;②不要眼高手低,自己跟着敲一遍才真正有收获;③点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
- 良工不示人以朴,up所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!
二、AOP—快速入门
1.动态代理优化 :
“书接上回”,在一个问题——假如将匿名内部类实现的invoke方法中的输出语句,都替换成方法,用一个方法直接切入,那不就既满足灵活性,又可以实现强大的功能吗?
一文最后,我们提出那现在我们就来尝试一个简单的案例,我们自己定义一个简单的AOP类,并在该类中定义用于切入的静态方法(这么做可以达到降低耦合度的目的,而不是直接把方法定义在提供代理对象的类中);up以CyanAOP类为例,CyanAOP类代码如下 :
接着,在CalculatorProxyProvider类中调用CyanAOP类中的静态方法,取代原有的输出语句;
CalculatorProxyProvider类代码如下 :
然后,在测试类中运行单元测试方法,测试动态代理是否生效,测试类TestCalculator代码如下 :
运行结果 :
2.问题分析 :
上文中我们通过自定义简单的AOP类,成功用较为灵活的“方法” 替换了 非常死板的“输出语句”。但是,新的问题出现了—— 这种改进方式依然不够灵活,功能依然弱鸡,并且复用性也比较差。
新的问题体现在哪里呢?
(1) 当前方案下,比如我们在测试类中调用了add方法和subtract方法,这俩个方法都受到了CyanAOP类中的静态方法的影响,但是,假如我们此时仅希望针对于add方法的控制,显然当前方案是做不到的。即,我们无法针对于某个或某些指定的方法进行控制,不够灵活。
(2) 当前方案下,CyanAOP类中定义的静态方法只是适用于Calculator接口和它的实现类;假如up此时又定义了其他的接口,那么CyanAOP类中定义的静态方法便无法满足新的需求。即,我们无法对于其他接口及其实现类进行切入控制,代码复用性差。
(3) 我们要明白,之所以存在以上问题,是由于当前方案缺少 “注解 + 反射机制” 的支持。
3.AOP—基本介绍 :
AOP : Aspect Oriented Programming,即面向切面编程。
我们知道,在传统的OOP中,某个类所定义的成员方法,只能被该类对象或该类的子类对象所调用;即我们先通过new关键字创建一个对象,亦可构成多态,然后通过 "对象." 的形式来调用类中的方法。
但是在AOP,我们是通过 “动态代理 + 反射” 的形式来调用类中的方法,那么类中的方法在执行过程中会被分为多个横切关注点,包括前置通知,返回通知,异常通知,后置通知。我们可以定义切面类,切面类中定义了用于切入的方法,通过注解配置,这些方法可以切入到任意支持AOP的类的任意方法的横切关注点。如下图所示 :
4.AOP—使用说明 :
(1) 要想使用Spring 的AOP注解,需要导入以下几个jar包,如下图所示 :
此外,需要引入核心aspects.jar包,如下图所示 : (在Spring安装目录的libs目录下可以找到)
(2) 常用的用于声明通知方法的五大注解如下——
1> @Before : 前置通知;
2> @AfterReturning : 返回通知;
3> @AfterThrowing : 异常通知;
4> @After : 后置通知;
5> @Around : 环绕通知[可以将四个通知合并管理];
官方文档的解释如下 : (Spring Framework 6.1.2)
5.AOP—入门案例 :
需求 : 定义切面类CalculatorAspect,并在切面类中定义两个方法,利用通知注解, 将这两个方法分别切入到Calculator_Demo1类的add方法执行之前 和 执行之后,即分别作为前置通知 和 返回通知。PS : 需要在xml配置文件中配置注解扫描。
up先在aop.aspectJ包下拷贝/创建 这么几个类,如下图所示 :
Calculator接口代码如下 :
Calculator_Demo1类代码如下 :
切面类CalculatorAspect代码如下 : (注意看代码注释)
为了获取到bean对象,我们需要在xml配置文件中配置基于注解的组件扫描。up以beans_aop.xml类为例,代码如下 :
接着,我们仍在该包下定义一个测试类TestAspectJ,测试切面类中的方法是否成功切入到指定位置,TestAspectJ类代码如下 :
运行结果 :
三、AOP—切入点表达式
1.基本说明 :
实际上,我们在上文 “AOP—入门案例” 中所用到的 “execution(public double com.cyan.spring.aop.aspectJ.Calculator_Demo1.add(double, double))” 就是一个切入点表达式。
其作用是——通过表达式的方式定位一个或多个具体的的连接点。
2.语法格式 :
(1) "execution(access_modifier return_type 全类名 方法名(形参列表))"
(2) Spring AOP的切入点表达式非常灵活,支持模糊配置。
eg1 : execution(* 全类名.*(..))——第一个 "*" 表示支持任意修饰符及返回值类型;第二个 "*" 表示支持该类中的任意方法;形参列表中的".."则表示可以匹配任意数量和类型的参数。(PS : 若目标类、接口与当前切面类在同一个包下,可以省略包名,只写类名)
eg2 : execution(public * 全类名.*(..))——表示支持该类中的所有公有的方法
eg3 : execution(public double 全类名.*(..))——表示支持该类中所有公有的且返回值为double的方法
eg4 : execution(public double 全类名.*(double, ..))——表示支持该类中所有形参列表第一个参数为double类型,且后续参数可以是任意数量任意类型的,公有的返回值为double的方法。
eg5 : execution(double 全类名.*(double, double)——表示支持该类中所有形参列表为两个double类型,公有的且返回值为double类型的方法。
(3) 在AspectJ中,切入点表达式可以通过"&&","||","!"等操作符结合起来。
eg : execution(* *.add(int, ..)) || execution(* *.subtract(int, ..))——表示支持任意类中的任意访问修饰符和任意返回值类型的,且形参列表第一个参数为int类型的add 或 subtract方法。
3.注意事项 :
(1) 当切入点表达式直接指向了接口某个实现类的方法(非实现类特有方法),这时切入点表达式仅会对该实现类生效(动态代理 + 反射),即接口的其他实现类不会生效(不会得到代理对象,即使你以接口类型作为接收)。
(2) 当切入点表达式指向接口的方法时,切入表达式会对该接口的所有实现类生效。
(3) 切入点表达式也可以切入到没有实现接口的类的横切关注点中。(CGlib动态代理模式)
PS : JDK Proxy动态代理和CGlib动态代理的区别——
JDK动态代理是面向接口的,只能增强实现类中重写了接口中的方法。而CGlib是面向父类的,可以增强父类的所有方法。
JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类。
4.代码演示 :
先来演示"注意事项"中的第一点,up新定义一个Calculator接口的实现类Calculator_Demo2类,代码如下 : (用@Component注解标记为组件,且给出了自定义的id值 = calculator02)
切面类保持不变,即配置的切入表达式仍然直接指向了Calculator_Demo1类。
接着,up定义一个测试方法,代码如下 :
运行结果 :
可以看到,只有Calculator_Demo1类成功得到了代理对象,且配置的切入点表达式也执行成功,而Calculator_Demo2类却仍然只是一个实现类对象。
注意,这时候,up将切面类中原来的@Before前置通知的代码注释掉,重新定义一个@Before前置通知,令切入表达式指向Calculator接口,代码如下 :
这时,再次运行刚才的测试方法,会发现Calculator_Demo1类和Calculator_Demo2类都可以成功获得代理对象,并且配置的@Before前置通知也成功执行,运行结果如下图所示 :
最后来演示一下注意事项3——即CGlib动态代理的使用。
up新定义一个Cat类,代码如下 :
然后,再新定义一个切面类,CatAspect类代码如下 :
最后,再定义一个简单的测试类,测试CGlib动态代理是否生效,CatTest类代码如下 :
运行结果 :
可以看到,切面类中定义的前置通知成功执行!
四、AOP—切入点表达式的更多细节
1.JoinPoint :
1.1 简介
JoinPoint,即连接点对象,通过该对象可以获取到调用的Method的签名,即方法签名对象Signature。
1.2 方法
//(1) 获取方法名
joinPoint.getSignature().getName();
//(2) 获取简单类名
joinPoint.getSignature().getDeclaringType().getSimpleName();
//(3) 获取全类名
joinPoint.getSignature().getDeclaringTypeName();
//(4) 获取访问权限修饰符(int类型)
joinPoint.getSignature().getModifiers();
//(5) 获取方法的参数,用数组作接收。
Object[] args = joinPoint.getArgs();
//(6) 获取被代理的对象
joinPoint.getTarget();
//(7) 获取代理对象本身
joinPoint.getThis();
1.3 演示
我们就在刚才的CatAspect中测试这些方法,CatAspect类代码如下 :
运行结果 :
2.通知进阶 :
2.1 通过@AfterReturning获取方法的返回结果
在使用@AfterReturning注解配置“返回通知”时,可以在配置切入点表达式后,额外通过returning = "表示返回结果的变量"来定义方法的返回结果,但需要在切入方法的形参列表中,同时也额外定义一个相同名称的变量。如下图所示 :
我们可以定义一个单元测试方法,测试方法的返回结果是否成功传递到了result变量中,代码如下 :
运行结果 :
2.2 通过@AfterThrowing获取异常信息
在使用@AfterThrowing注解配置“返回通知”时,可以在配置切入点表达式后,额外通过throwing= "表示异常信息的变量"来定义方法的异常信息,但需要在切入方法的形参列表中,同时也额外定义一个相同名称的变量(注意变量的类型)。如下图所示 :
我们可以在Calculator_Demo2类的add方法中,故意制造一个算术异常,如下图所示 :
然后,定义一个单元测试方法,测试“异常通知”是否成功获取了异常信息。代码如下 :
运行结果 :
2.3 环绕通知
环绕通知可以将四个通知合并管理,它可以完成其他四个通知的功能(前置通知,返回通知,异常通知,后置通知)。
我们新定义一个切面类CalculatorAspect_EX,用于配置环绕通知;为了防止之前配置的其他通知对测试结果造成干扰,up把之前定义的切面类CalculatorAspect类先给注释掉了。
CalculatorAspect_EX类代码如下 :
然后,up再定义一个单元测试方法,用于测试环绕通知是否生效,代码如下 :
运行结果 :
可以看到,通过环绕通知,成功达到了“前置通知”,“返回通知”,“后置通知”的效果。
3.切入点表达式重用(@Pointcut) :
3.1 简介
我们在之前定义的切面类CalculatorAspect类中定义了多个切入点表达式,但是我们发现这样一个问题——多个切入点表达式都指向了相同的类的相同方法,未免有些冗余的感觉,能不能想办法将这些切入点表达式进行复用,以提高使用效率呢?
为了统一管理切入点表达式,我们可以使用切入点表达式重用技术(使用@Pointcut注解)。其使用格式如下图所示 :
3.2 实例
需求 : 我们就在原来的CalculatorAspect切面类中进行修改,要求通过@Pointcut注解配置一个切入点表达式,然后对该表达式重用,完成前置通知和返回通知。
实现 : 首先,为防止对输出结果造成干扰,up先将上文“环绕通知”中定义的CalculatorAspect_EX切面类注释掉。然后在CalculatorAspect类中进行配置,代码如下 :
接着,仍然在测试类TestAspectJ中定义一个单元测试方法,测试切入表达式重用是否成功,代码如下 :
运行结果 :
4.切面类执行顺序 :
4.1 简介
(1) 若多个切面类中定义的切入方法指向了同一个横切关注点,可以通过@Order(value=n)注解来控制切面类执行的优先级,其中,n的值越小,则该切面类执行的优先级越高。PS : 注意,是org.springframework.core.annotation包下的@Order注解。
(2) 多个切面类的执行顺序,其实和FilterChain过滤器链的执行顺序是十分相似的,即后调用的先执行完毕,如下图所示 :
4.2 实例
新定义一个切面类——CalculatorAspect2类,并修改该切面类中前置通知和返回通知的输出信息,以和CalculatorAspect切面类作区分。CalculatorAspect2类代码如下 :
这时,若我们运行测试方法(切入表达式重用中定义的测试方法),会发现两个切面类的切入方法同时生效,运行结果如下图所示 :
可以看到,当前是旧的切面类先生效;现在,我们给两个切面类都配置上@Order注解,如下图所示 :
可以看到,此时新配置的切面类CalculatorAspect2的执行优先级更高;这时我们重新运行测试方法,运行结果如下 :
可以看到,果然是CalculatorAspect2切面类先执行。
5.基于XML配置AOP :
到现在为止,我们一直都在使用“基于注解配置AOP”的形式,那么基于XML又怎么配置AOP呢?
up将原先的beans_aop.xml配置文件进行修改,将注解扫描的代码注释掉,并在该配置文件中基于XML配置AOP,beans_aop.xml代码如下 :
重新运行测试方法,会发现依然成功,如下图所示 :
五、总结
- 🆗,以上就是Spring系列博文第五小节的全部内容了。
- 总结一下,我们先从“动态代理的优化”引出,自己定义了一个AOP类进行模拟,进行问题分析后(不够灵活,复用性差);又通过一个入门案例了解了AOP——通知注解的使用。接着,我们又详细介绍了AOP——切入点表达式,包括它的基本使用,注意事项和更多细节;最后,我们又简单介绍了基于XML方式配置AOP。
- 下一节内容——Spring 手动实现Spring底层机制,我们不见不散😆。感谢阅读!
System.out.println("END--------------------------------------------------");
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
作者其他文章
评论(0)