前言
本文主要跟大家分享介绍了关于Spring AOP中@Aspect的高级用法,下面话不多说了,来随着小编一起看看详细的介绍吧。
1 切点复合运算
支持在切点定义中加入以下运算符进行复合运算:
运算符 | 说明 |
---|---|
&& | 与运算。 |
! | 非运算。 |
|| | 或运算。 |
2 切点命名
一般情况下,切点是直接声明在需要增强方法处,这种切点的声明方式称为匿名切点,匿名切点只能在声明处被使用 。 如果希望在其它地方可以重用这个切点,我们可以通过 @Pointcut 注解及切面类方法来命名它。
public class NamePointcut { /** * 切点被命名为 method1,且该切点只能在本类中使用 */ @Pointcut("within(net.deniro.spring4.aspectj.*)") private void method1() { } /** * 切点被命名为 method2,且该切点可以在本类或子孙类中使用 */ @Pointcut("within(net.deniro.spring4.aspectj.*)") protected void method2() { } /** * 切点被命名为 method3,且该切点可以在任何类中使用 * 这里还使用了复合运算 */ @Pointcut("method1() && method2()") public void method3() { } }
命名切点的结构如下:
切点可访问性修饰符与类可访问性修饰符的功能是相同的,它可以决定定义的切点可以在哪些类中可使用。
因为命名切点仅利用方法名及访问修饰符的信息,所以我们一般定义方法的返回类型为 void ,并且方法体为空 。
定义好切点后,就可以在切面类中引用啦:
@Aspect public class NamePointcutAspect { @After("NamePointcut.method2()") public void aspectMethod1() { } /** * 这里使用了复合运算 */ @After("NamePointcut.method2() && NamePointcut.method3()") public void aspectMethod2() { } }
3 织入顺序
一个连接点可以同时匹配多个切点,而切点所对应的增强在连接点上织入顺序的规则是这样的:
1.如果在同一个切面类中声明的增强,则按照增强在切面类中定义的顺序进行织入;
2.如果增强位于不同的切面类中,并且这些切面类都实现了org.springframework.core.Ordered 接口,则由 Ordered 方法的顺序号决定(顺序号小的先织入);
3.如果增强位于不同的切面类中,但这些切面类没有实现org.springframework.core.Ordered 接口,织入的顺序是不确定的 。
假设有两个切面类 A 与 B,它们都实现了 Ordered 接口,A 的顺序号为 1,B 的顺序号为 2,切面类 A 与 B 都定义了 3 个增强,那么同时匹配这 6 个增强的织入顺序如下图所示:
4 获取连接点信息
4.1 JoinPoint
org.aspectj.lang.JoinPoint 接口表示目标类连接点对象,它定义这些主要方法。
方法 | 说明 |
---|---|
Object[] getArgs() | 获取连接点方法运行时的入参列表。 |
Signature getSignature() | 获取连接点的方法签名对象。 |
Object getTarget() | 获取连接点所在的目标对象。 |
Object getThis() | 获取代理对象。 |
4.2 ProceedingJoinPoint
org.aspectj.lang.ProceedingJoinPoint 继承了 JoinPoint 接口,它新增了两个方法(它们用于执行连接点方法)。
方法 | 说明 |
---|---|
Object proceed() throws Throwable | 通过反射执行目标对象连接点处的方法。 |
Object proceed(Object[] var1) throws Throwable | 使用新的入参(替换掉原来的入参),通过反射执行目标对象连接点处的方法。 |
4.3 示例
Cook 接口:
public interface Cook { /** * 制作食品 */ void make(); /** * 制作 * * @param name 食品名称 */ void make(String name); }
CookA 类:
public class CookA implements Cook { public void make() { System.out.println("制作食品"); } public void make(String name) { System.out.println("制作" + name); } }
在切面类中访问连接点信息:
@Aspect public class JoinPointAspect { @Around("within(net.deniro.spring4.aspectj.CookA)") public void test(ProceedingJoinPoint pjp) throws Throwable { System.out.println("---------获取连接点对象【开始】---------"); System.out.println("参数:" + pjp.getArgs()[0]); System.out.println("签名对象:" + pjp.getTarget().getClass()); //执行目标对象方法 pjp.proceed(); System.out.println("---------获取连接点对象【结束】---------"); } }
Spring bean 配置:
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--aspectj 驱动器 --> <aop:aspectj-autoproxy/> <bean id="cookA" class="net.deniro.spring4.aspectj.CookA"/> <bean class="net.deniro.spring4.aspectj.JoinPointAspect"/> </beans>
输出结果:
---------获取连接点对象【开始】---------
参数:寿司
签名对象:class net.deniro.spring4.aspectj.CookA
制作寿司
---------获取连接点对象【结束】---------
5 绑定连接点的方法入参
args()、this()、target()、@args()、@within()、@target() 和 @annotation() 这些切点函数除可以指定类名外,还可以指定参数名,将目标对象连接点上的方法入参绑定到增强的方法中 。 其中 args() 用于绑定连接点方法的入参, @annotation() 用于绑定连接点方法的注解对象,而 @args() 用于绑定连接点方法入参的注解。
CookC 类:
public class CookC implements Cook { public void make() { System.out.println("制作食品"); } public void make(String name) { System.out.println("制作" + name); } public void make(String name, int num) { System.out.println("制作" + name + " " + num + " 个"); } }
切面类:
@Aspect public class ParamsAspect { @Before("target(net.deniro.spring4.aspectj.CookC) && args(name,num,..)") public void test(String name,int num) { System.out.println("----------绑定连接点入参【开始】----------"); System.out.println("name:" + name); System.out.println("num:" + num); System.out.println("----------绑定连接点入参【结束】----------"); } }
切点匹配和参数绑定的过程是这样的:
上述示例中的匹配过程如下:
Spring 配置:
<!--aspectj 驱动器 --> <aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="cookC" class="net.deniro.spring4.aspectj.CookC"/> <bean class="net.deniro.spring4.aspectj.ParamsAspect"/>
注意: 这里必须通过 <aop:aspectj-autoproxy proxy-target-class="true" />来启用 CGLib 动态代理,这是因为 CookC 的 public void make(String name, int num) 是该类独有的方法(非接口定义的方法),所以必须使用 CGLib 生成子类的代理方法 。
单元测试:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); CookC cookC = (CookC) context.getBean("cookC"); cookC.make("寿司", 100);
输出结果:
----------绑定连接点入参【开始】----------
name:寿司
num:100
----------绑定连接点入参【结束】----------
制作寿司 100 个
6 绑定代理对象
使用 this() 或 target() 可绑定被代理对象的实例。通过类实例名绑定对象时,依然具有原来连接点匹配的功能,只是类名是由增强方法中的同名入参类型间接决定的。
@Aspect public class ProxyAspect { @Before("this(cook)") public void bind(Cook cook) { System.out.println("--------绑定代理对象【开始】--------"); System.out.println(cook.getClass().getName()); System.out.println("--------绑定代理对象【结束】--------"); } }
首先通过 public void bind(Cook cook) 找出 cook 所对应的类型,接着转换切点表达式为 this(net.deniro.spring4.aspectj.Cook) 。这样就表示该切点匹配所有代理对象为 Cook 类中的所有方法。
输出结果:
--------绑定代理对象【开始】--------
net.deniro.spring4.aspectj.CookC$$EnhancerBySpringCGLIB$$217fb793
--------绑定代理对象【结束】--------
target() 绑定与 this() 相似。
7 绑定类注解对象
@within() 和 @target() 函数都可以将目标类的注解对象绑定到增强方法中。
定义一个日志注解类:
@Retention(RetentionPolicy.RUNTIME)//保留期限 @Target({ElementType.METHOD,ElementType.TYPE})//目标类型 public @interface Log { boolean value() default true;//声明成员变量 }
把该注解类应用于 CookD:
@Log public class CookD implements Cook { public void make() { System.out.println("制作糕点"); } public void make(String name) { } }
绑定类注解对象:
@Aspect public class ClassAnnotationObjectAspect { @Before("@within(log)") public void bind(Log log){ System.out.println("----------绑定类注解对象【开始】----------"); System.out.println(log.getClass().getName()); System.out.println("----------绑定类注解对象【结束】----------"); } }
Spring 配置:
<!--aspectj 驱动器 --> <aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="cookD" class="net.deniro.spring4.aspectj.CookD"/> <bean class="net.deniro.spring4.aspectj.ClassAnnotationObjectAspect"/>
单元测试:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); CookD cook = (CookD) context.getBean("cookD"); cook.make();
输出结果:
----------绑定类注解对象【开始】----------
com.sun.proxy.$Proxy8
----------绑定类注解对象【结束】----------
从输出结果 com.sun.proxy.$Proxy8 可以看出 ,CookD 类的注解 Log 对象也被代理咯O(∩_∩)O哈哈~
8 绑定返回值
在后置增强中,可以通过 returning 来绑定连接点方法的返回值。
切面:
@Aspect public class ReturnValueAspect { @AfterReturning(value = "target(net.deniro.spring4.aspectj.CookA)", returning = "value") public void bind(boolean value) { System.out.println("绑定返回值【开始】"); System.out.println("value:" + value); System.out.println("绑定返回值【结束】"); } }
注意:returning 的值必须与方法参数名相同。
CookA 新增 smell 方法:
public boolean smell(String name) { System.out.println(name + "香吗?"); return true; }
单元测试:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); CookA cook = (CookA) context.getBean("cookA"); cook.smell("烤鸭");
输出结果:
烤鸭香吗?
绑定返回值【开始】
value:true
绑定返回值【结束】
9 绑定异常
可以使用 AfterThrowing 注解的 throwing 成员变量来绑定连接点抛出的异常。
切面类:
@Aspect public class ExceptionAspect { @AfterThrowing(value = "target(net.deniro.spring4.aspectj.CookA)",throwing = "e") public void bind(CookException e){ System.out.println("绑定异常【开始】"); System.out.println("e:" + e.getMessage()); System.out.println("绑定异常【结束】"); } }
注意:throwing 的值必须与方法参数名相同。
单元测试:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); CookA cook = (CookA) context.getBean("cookA"); cook.make("");
输出结果:
绑定异常【开始】
e:煮啥呢???
绑定异常【结束】
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对小牛知识库的支持。
本文向大家介绍浅谈junit4单元测试高级用法,包括了浅谈junit4单元测试高级用法的使用技巧和注意事项,需要的朋友参考一下 Junit单元测试框架是Java程序开发必备的测试利器,现在最常用的就是Junit4了,在Junit4中所有的测试用例都使用了注解的形式,这比Junit3更加灵活与方便。之前在公司的关于单元测试的培训课程中,讲师仅仅讲述了Junit4的基本的与生命周期相关的注解的使用,主
本文向大家介绍谈谈java的concurrent用法,包括了谈谈java的concurrent用法的使用技巧和注意事项,需要的朋友参考一下 我们都知道,在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便。而当针对高质量Java多线程并发程序设计时,为防止死蹦等现象的出现,比如使用jav
本文向大家介绍详细谈谈AngularJS的子级作用域问题,包括了详细谈谈AngularJS的子级作用域问题的使用技巧和注意事项,需要的朋友参考一下 前言 AngularJS自带指令目前有ng-include、ng-view、ng-switch、ng-repeat。这样的原因是因为,这些指令虽然是AngularJS内部定义的,但是也是和directive实现的方法都是一样的,其内部使用的是scope
本文向大家介绍浅谈Android Service服务的高级技巧,包括了浅谈Android Service服务的高级技巧的使用技巧和注意事项,需要的朋友参考一下 1 前台服务 因为服务的优先级较低,所以当系统内存不足时,可能会回收正在后台运行的服务。如果若要避免服务被回收,可以使用前台服务。 前台服务会一直有一个图标在系统的状态栏中显示,下拉状态栏可以看到更加详细的信息,类似于消息通知效果。 在此构
本文向大家介绍谈谈你对webpack的看法相关面试题,主要包含被问及谈谈你对webpack的看法时的应答技巧和注意事项,需要的朋友参考一下 WebPack 是一个模块打包工具,你可以使用WebPack管理你的模块依赖,并编绎输出模块们所需的静态文件。它能够很好地管理、打包Web开发中所用到的HTML、JavaScript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资
本文向大家介绍浅谈keras中的keras.utils.to_categorical用法,包括了浅谈keras中的keras.utils.to_categorical用法的使用技巧和注意事项,需要的朋友参考一下 如下所示: to_categorical(y, num_classes=None, dtype='float32') 将整型标签转为onehot。y为int数组,num_classes为标