基于@AspectJ配置Spring AOP之一
within()
通过类匹配模式串声明切点,within()函数定义的连接点是针对目标类而言,而非针对运行期对象的类型而言,这一点和execetion()是相同的。但和execution()函数不同的是,within()所指定的连接点最小范围只能是类,而execution()所指定的连接点,可以大到包,小到方法入参。所以从某种意义上说,execution()函数的功能涵盖了within()函数的功能。within()函数的语法如下所示:
形如within(com.baobaotao.NaiveWaiter)是within()函数所能表达的最小粒度,如果试图用within()匹配方法级别的连接点,如within(com.baobaotao.NaiveWaiter.greet*)将会产生解析错误。within(<类匹配模式>)
下面是一些使用within()函数的实例:
within(com.baobaotao.NaiveWaiter)
匹配目标类NaiveWaiter的所有方法。如果切点调整为within(com.baobaotao.Waiter),则NaiveWaiter和NaughtyWaiter中的所有方法都不匹配,而Waiter本身是接口不可能实例化,所以within(com.baobaotao.Waiter)的声明是无意义的;
within(com.baobaotao.*)
匹配com.baobaotao包中的所有类,但不包括子孙包,所以com.baobaotao.service包中类的方法不匹配这个切点;
within(com.baobaotao..*)
匹配com.baobaotao包及子孙包中的类,所以com.baobaotao.service、com.baobaotao.dao以及com.baobaotao.service.fourm等包中所有类的方法都匹配这个切点。
@within()和@target()
除@annotation()和@args()外,还有另外两个用于注解的切点函数,它们分别是@target()和@within(),和@annotation()及@args()函数一样,它们也只接受注解类名作为入参。其中@target(M)匹配任意标注了@M的目标类,而@within(M)匹配标注了@M的类及子孙类。
@target(M)切点的匹配规则如图 6所示:

图 6 @target(M)匹配目标类示意图
假设NaiveWaiter标注了@Monitorable,则其子类CuteNaiveWaiter没有标注@Monitorable,则@target(com.baobaotao.Monitorable)匹配NaiveWaiter类的所有方法,但不匹配CuteNaiveWaiter类的方法。
@within(M)切点的匹配规则如图 7所示:

图 7 @within(M)匹配目标类示意图
假设NaiveWaiter标注了@Monitorable,而其子类CuteNaiveWaiter没有标注@Monitorable,则@within(com.baobaotao.Monitorable)不但匹配NaiveWaiter类中的所有方法也匹配CuteNaiveWaiter类中的所有方法。
但有一个特别值得注意地方是,如果标注@M注解的是一个接口,则所有实现该接口的类并不匹配@within(M)。假设Waiter标注了@Monitorable注解,但NaiveWaiter、NaughtyWaiter及CuteNaiveWaiter这些接口实现类都没有标注@Monitorable,则@within(com.baobaotao.Monitorable)和@target(com.baobaotao.Monitorable)都不匹配NaiveWaiter、NaughtyWaiter及CuteNaiveWaiter。这是因为@within()、@target()以及@annotation()都是针对目标类而言,而非针对运行时的引用类型而言,这点区别需要在开发中特别注意。
target()的this()
target()切点函数通过判断目标类是否按类型匹配指定类决定连接点是否匹配,而this()则通过判断代理类是否按类型匹配指定类来决定是否和切点匹配。两者都仅接受类名的入参,虽然类名可以带“+”通配符,但对于这两个函数来说,使用与不使用+通配符,效果完全相同。
1)target()
target(M)表示如果目标类按类型匹配于M,则目标类所有方法匹配切点,我们通过一些例子理解target(M)的匹配规则:
target(com.baobaotao.Waiter)
NaiveWaiter、NaughtyWaiter以及CuteNaiveWaiter的所有方法都匹配切点,包括那些未在Waiter接口中定义的方法,如NaiveWaiter#simle()和NaughtyWaiter#joke()方法。
target(com.baobaotao.Waiter+)
和target(com.baobaotao.Waiter)是等价的。
2)this()
根据Spring的官方文档,this()函数判断代理对象的类是否按类型匹配于指定类,如果匹配,则代理对象的所有连接点匹配切点。但通过实验,我们发现实际情况和文档有出入,如我们声明一个this(com.baobaotao.NaiveWaiter)的切点,如果不使用CGLib代理,则生成的代理对象是Waiter类型,而非NaiveWaiter类型,这一点可以简单地通过instanceof操作符进行判断。但是,我们发现NaiveWaiter中所有的方法还是被织入了增强。
在一般情况下,使用this()和target()通过定义切点,两者是等效的:
1) target(com.baobaotao.Waiter) 等价于this(com.baobaotao.Waiter)
2) target(com.baobaotao.NaiveWaiter) 等价于 this(com.baobaotao.NaiveWaiter)
两者区别体现在通过引介切面产生的代理对象时的具体表现,如果我们通过本文前面的方法为NaiveWaiter引介一个Seller接口的实现,则this(com.baobaotao.Seller)匹配NaiveWaiter代理对象的所有方法,包括NaiverWaiter本身的greetTo()、serverTo()方法以及通过Seller接口引入的sell()方法。而target(com.baobaotao.Seller)不匹配通过引介切面产生的NaiveWaiter代理对象。
下面通过具体的实例来了解这一微妙的区别,EnableSellerAspect是为NaiveWaiter添加Seller接口实现的引介切面:
TestAspect是通过判断运行期代理对象所属类型来定义切点的切面:package com.baobaotao.aspectj.fun; … @Aspect public class EnableSellerAspect{ @DeclareParents(value = "com.baobaotao.NaiveWaiter", defaultImpl = SmartSeller.class) public static Seller seller; }
代码清单 6 TestAspect:通过this()指定切点
在Spring中配置这两个切面和NaiveWaiter:package com.baobaotao.aspectj.fun; … @Aspect public class TestAspect { @AfterReturning("this(com.baobaotao.Seller)") ①后置增强,织入到任何运行期对象为 Seller 类型的Bean中 public void thisTest(){ System.out.println("thisTest() executed!"); } }
<aop:aspectj-autoproxy/>
<bean id="naiveWaiter" class="com.baobaotao.NaiveWaiter" />
<bean class="com.baobaotao.aspectj.fun.EnableSellerAspect"/>
<bean class="com.baobaotao.aspectj.fun.TestAspect" />
首先EnableSellerAspect切面为NaiveWaiter引介Seller接口产生一个实现Seller接口的代理对象,TestAspect在判断出NaiveWaiter这个代理对象实现Seller接口后,就将其切面织入到这个代理对象中,所以最终NaiveWaiter的代理对象其实共织入了两个切面。
运行以下的测试代码:
输出以下的代码:String configPath = "com/baobaotao/aspectj/fun/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter naiveWaiter = (Waiter) ctx.getBean("naiveWaiter"); naiveWaiter.greetTo("John"); naiveWaiter.serveTo("John"); ((Seller)naiveWaiter).sell("Beer", "John");
可见代理对象的3个方法都织入了代码清单 6通过this()所定义切面。NaiveWaiter:greet to John... thisTest() executed! NaiveWaiter:serving John... thisTest() executed! SmartSeller: sell Beer to John... thisTest() executed!
小结
使用@AspectJ定义切面比基于接口定义的切面更加直观、更加简洁,成为Spring所推荐的切面定义方式。掌握切点表达式语法和切点函数是学习@AspectJ的重心,我们分别对9个切点函数进行了详细的讲述。切点表达式非常灵活,拥有强大的切点表达能力,你可以使用通配符、切点函数以及切点运算符定义切点。
Spring 2.0在AOP上花费了很大的功夫,相比于低版本的Spring,我们看到了很大的改进。在掌握低版本Spring AOP相关知识的基础上,你会发现学习Spring 2.0 基于@AspectJ AOP的新知识不会有太多的门槛。
0
相关文章