技术开发 频道

基于@AspectJ配置Spring AOP之一


在函数入参中使用通配符

有些函数的入参可以接受通配符,@AsppectJ支持三种通配符:
 * 匹配任意字符,但它只能匹配上下文中的一个元素;
 .. 匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联合使用,而在表示入参时则单独使用;
 + 表示按类型匹配指定类的所有类,仅能跟在类名后面。

@AspectJ函数按其是否可以支持通配符及支持的程度,可以为以下3类:
 支持所有通配符:execution()、within(),如within(com.baobaotao.*)、within(com.baobaotao.service..*.*Service+)等;
 仅支持+通配符:args()、this()、target(),如args(com.baobaotao.Waiter+)、target(java.util.List+)等。虽然这三个函数可以支持+通配符,但其意义不大,因为对于这些函数来说使用和不使用+都是一样的,如target(com.baobaotao.Waiter+)和target(com.baobaotao.aspectj.Waiter)是等价的。
 不支持通配符:@args()、@within()、@target()和@annotation(),如@args(com.baobaotao.anno.NeedTest),@within(com.baobaotao.anno.NeedTest)。

此外,args()、this()、target()、@args()、@within()、@target()和@annotation()这7个函数除了可以指定类名外,也可以指定变量名,并将目标对象中变量绑定到增强的方法中。

逻辑运算符

切点表达式由切点函数组成,切点函数之间还可以进行逻辑运算,组成复合切点,Spring支持以下的切点运算符:
 && 与操作符,相当于切点的交集运算,由于也可以在Spring配置文件中使用切点表示式,而&是XML特殊字符,需要进行特殊表示,为了方便使用,Spring提供了一个等效的运算符“and”。如within(com.baobaotao..*) && args(String)表示在com.baobaotao包下所有类(当前包以及子孙包)拥有一个String入参的方法;
 || 或操作符,相当于切点的并集运算,or是等效的操作符。如within(com.baobaotao..*) || args(String)表示在com.baobaotao包下所有类的方法,或者所有拥有一个String入参的方法;
 ! 非操作符,相当于切点的反集运算,not是等效的操作符。如!within(com.baobaotao.*)表示所有不在com.baobaotao包下的方法。

在标准的@AspectJ中并不提供and、or和not操作符,它们是Spring为了在XML配置文件中定义切点表达式而特意添加的等价操作符。有意思的是,和一般语法表达不一样,在Spring中使用and、or和not时,允许不在前后添加空格,如:within(com.baobaotao..*)andnotargs(String)和within(com.baobaotao..*) and not args(String)拥有相同的效果。虽然Spring接受这种表示方式,但为了保证程序的可读性,最好还是采用传统习惯,在操作符的前后添加空格。

提示 如果not位于切点表达式的开头,则必须在开头添加一个空格,否则将产生解析错误。如"not within(com.baobaotao..*)"将产生解析错误,这应该是Spring解析的一个Bug,在表达式开头添加空格后则可以通过解析:" not within(com.baobaotao..*)"。
不同增强类型

在低版本的Spring AOP中,你可以通过实现不同的增强接口定义各种类型的增强类, @AspectJ也为各种类型的增强提供了不同的注解类,它们位于org.aspectj.lang.annotation.*包中。这些注解类拥有若干个成员,可以通过这些成员完成定义切点信息、绑定连接点参数等操作。此外,这些注解的存留期限都是RetentionPolicy.RUNTIME,标注目标都是ElementType.METHOD。下面我们逐一学习@AspectJ所提供的几个增强注解。

@Before
前置增强,相当于BeforeAdvice的功能,Before注解类拥有2个成员:
 value:该成员用于定义切点;
 argNames:由于无法通过Java反射机制获取方法入参名,所以如果在Java编译时未启用调试信息或者需要在运行期解析切点,就必须通过这个成员指定注解所标注增强方法的参数名(注意两者名字必须完全相同),多个参数名用逗号分隔。

@AfterReturning
后置增强,相当于AfterReturningAdvice,AfterReturning注解类拥有4个成员:
 value:该成员用于定义切点;
 pointcut:表示切点的信息,如果显式指定pointcut值,它将覆盖value的设置值,可以将pointcut成员看成是value的同义词;
 returning:将目标对象方法的返回值绑定给增强的方法;
 argNames:如前所述。

@Around
环绕增强,相当于MethodInterceptor,Around注解类拥有2个成员:
 value:该成员用于定义切点;
 argNames:如前所述。

@AfterThrowing
抛出增强,相当于ThrowsAdvice,AfterThrowing注解类拥有4个成员:
 value:该成员用于定义切点;
 pointcut:表示切点的信息,如果显式指定pointcut值,它将覆盖value的设置值,可以将pointcut成员看成是value的同义词;
 throwing:将抛出的异常绑定到增强方法中;
 argNames:如前所述。

@After
Final增强,不管是抛出异常或者是正常退出,该增强都会得到执行,该增强没有对应的增强接口,可以把它看成ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源,相当于try{}finally{}的控制流。After注解类拥有2个成员:
 value:该成员用于定义切点;
 argNames:如前所述。

@DeclareParents
引介增强,相当于IntroductionInterceptor,DeclareParents注解类拥有2个成员:
 value:该成员用于定义切点,它表示在哪个目标类上添加引介增强;
 defaultImpl:默认的接口实现类。

除引介增强外,其它增强都很容易理解,我们将在本文后续内容中统一讲述,但引介增强的使用比较特别,因为我们特别在下节中为其准备了一个实例。

引介增强用法
请看以下两个接口及其实现类,如图 2所示:

图 2 Waiter和Seller

假设我们希望NaiveWaiter能够同时充当售货员的角色,即通过切面技术为NaiveWaiter新增Seller接口的实现。我们可以使用@AspectJ的引介增强来实现这一功能。
代码清单 3 EnableSellerAspect
package com.baobaotao.aspectj.basic; 
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import com.baobaotao.Seller;
import com.baobaotao.SmartSeller;

@Aspect
public class EnableSellerAspect {
①为NaiveWaiter添加接口实现
@DeclareParents(value="com.baobaotao.NaiveWaiter",
defaultImpl=SmartSeller.class) ② 默认的接口实现类
public Seller seller; ③要实现的目标接口
}
    在EnableSellerAspect切面中,我们通过@DeclareParents为NaiveWaiter添加了一个需要实现的Seller接口,并指定其默认实现类为SmartSeller,然后通过切面技术将SmartSeller融合到NaiveWaiter中,这样NaiveWaiter就实现Seller接口了。
在Spring配置文件中配置好切面和NaiveWaiter Bean:
<aop:aspectj-autoproxy/> <bean id="waiter" class="com.baobaotao.NaiveWaiter"/> <bean class="com.baobaotao.aspectj.basic.EnableSellerAspect"/>
运行以下测试代码:
package com.baobaotao.aspectj.basic;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.baobaotao.Seller;
import com.baobaotao.Waiter;
public class DeclaredParentsTest ...{
public static void main(String[] args) ...{
String configPath = "com/baobaotao/aspectj/basic/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greetTo("John");
Seller seller = (Seller)waiter; ①可以成功进行强制类型转换
seller.sell("Beer", "John");
}
}

代码成功执行,并输出以下信息:
NaiveWaiter:greet to John...
SmartSeller: sell Beer to John...
可见,NaiveWaiter已经成功地新增了Seller接口的实现。
0
相关文章