基于AspectJ配置Spring AOP之一IT168技术文档.docx
《基于AspectJ配置Spring AOP之一IT168技术文档.docx》由会员分享,可在线阅读,更多相关《基于AspectJ配置Spring AOP之一IT168技术文档.docx(19页珍藏版)》请在冰豆网上搜索。
基于AspectJ配置SpringAOP之一IT168技术文档
基于@AspectJ配置SpringAOP之一
【IT168技术文档】
概述
在低版本Spring中定义一个切面是比较麻烦的,需要实现特定的接口,并进行一些较为复杂的配置,低版本SpringAOP的配置是被批评最多的地方。
Spring听取这方面的批评声音,并下决心彻底改变这一现状。
在Spring2.0中,SpringAOP已经焕然一新,你可以使用@AspectJ注解非常容易的定义一个切面,不需要实现任何的接口。
Spring2.0采用@AspectJ注解对POJO进行标注,从而定义一个包含切点信息和增强横切逻辑的切面,Spring2.0可以将这个切面织入到匹配的目标Bean中。
@AspectJ注解使用AspectJ切点表达式语法进行切点定义,可以通过切点函数、运算符、通配符等高级功能进行切点定义,拥有强大的连接点描述能力。
在你学习基于@AspectJ的切面技术后,恐怕你就再也没有兴趣使用低版本SpringAOP的实现技术了,毕竟马落桃花马前雪,两者的易用性、便捷性是不可同日而语的。
着手使用@AspectJ
我们知道在低版本的SpringAOP中,你必须使用Pointcut和Advice接口描述切点和增强,并用Advisor组合两者描述一个切面,@AspectJ则采用JDK5.0的注解技术描述切点和增强类型,而增强的横切逻辑就在被标注的POJO中定义。
使用前的准备
在使用@AspectJ之前,首先你得保证你所使用的JDK的版本是5.0及以上版本,否则无法使用注解技术。
Spring在处理@Aspect注解表达式时,需要使用位于/lib/asm/目录下asm的类包:
asm-2.2.2.jar、asm-commons-2.2.2.jar和asm-util-2.2.2.jar。
asm是轻量级的字节码处理框架,因为Java的反射机制无法获取入参名,Spring就利用asm处理@AspectJ中所描述的方法入参名。
此外,Spring采用AspectJ提供的@AspectJ注解类库及相应的解析类库,它位于/lib/aspectj目录下,将目录下的aspectjrt.jar和aspectjweaver.jar类包加入类路径中。
一个简单的例子
在做好上节中所提到的前置工作后,我们就可以开始编写一个基于@AspectJ的切面了,首先来看一个简单的例子,以便对@AspectJ有一个切身的认识。
下面,我们用@AspectJ注解对一个POJO进行标注,将使其成为一个切面类:
代码清单1PreGreetingAspect:
切面
packagecom.baobaotao.aspectj.aspectj;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Before;
@Aspect①通过该注解将PreGreetingAspect标识为一个切面
publicclassPreGreetingAspect{
@Before("execution(*greetTo(..))")②定义切点和增强类型
publicvoidbeforeGreeting(){③增强的横切逻辑
System.out.println("Howareyou");
}
}
我们“惊奇”地发现这个切面没有实现任何特殊的接口,它只是一个普通的POJO。
它特殊的地方在于使用了@AspectJ注解。
首先,在PreGreetingAspect类定义处,标注了一个@Aspectj注解,第三方处理程序就可以通过类是否拥有@Aspectj注解判断其是否是一个切面,如①所示。
其次,在beforeGreeting()方法标签处,标注了@Before注解,并为该注解提供了成员值"execution(*greetTo(..))",如②所示。
②处的注解提供了两个信息:
@Before注解表示该增强是前置增强,而成员值通过@ApsectJ切点表达式语法定义切点:
即在目标类的greetTo()方法上织入增强,greetTo()方法可以带任意的入参和任意的返回值。
最后,在③处的beforeGreeting()方法是增强的横切逻辑,该横切逻辑在目标方法前调用,我们通过下图描述这种关系:
图1切面的信息构成
PreGreetingAspect类通过注解和代码,将切点、增强类型和增强的横切逻辑揉合到一个类中,使切面的定义浑然天成。
如果在低版本SpringAOP中,你必须同时创建增强类,切点类以及切面类,并使三者联合表达相同的信息。
NaiveWaiter是一个Bean,它拥有一个greetTo()的方法,这个方法连接点匹配于上面我们通过@AspectJ所定义的切点,为了方便后续的说明,我们给出NaiveWaiter的代码:
packagecom.baobaotao;
publicclassNaiveWaiterimplementsWaiter{
publicvoidgreetTo(StringclientName){
System.out.println("NaiveWaiter:
greetto"+clientName+"...");
}
publicvoidserveTo(StringclientName){
System.out.println("NaiveWaiter:
serving"+clientName+"...");
}
}
下面,我们通过org.springframework.aop.aspectj.annotation.AspectJProxyFactory为NaiveWaiter生成织入PreGreetingAspect切面的代理,如代码清单2所示:
代码清单2AspectJProxyTest
packagecom.baobaotao.aspectj.example;
importorg.springframework.aop.aspectj.annotation.AspectJProxyFactory;
importcom.baobaotao.NaiveWaiter;
importcom.baobaotao.Waiter;
publicclassAspectJProxyTest{
Waitertarget=newNaiveWaiter();
AspectJProxyFactoryfactory=newAspectJProxyFactory();
factory.setTarget(target);①设置目标对象
factory.addAspect(PreGreetingAspect.class);②添加切面类
Waiterproxy=factory.getProxy();③生成织入切面的代理对象
proxy.greetTo("John");
proxy.serveTo("John");
}
}
Spring使用AspectJProxyFactory织入基于@AspectJ切面的工作。
在①处,设置了目标对象,在②处添加一个切面类,该类必须是带@AspectJ注解的类,在③处,我们就可以获取织入切面的代理对象了。
接下来,我们直接通过代理对象调用greetTo()和serveTo()代码,它们产生以下的输出信息:
Howareyou①表示greetTo()方法被成功地织入了切面
greettoJohn...
servingJohn...
通过①处的输出信息我们可以知道代理对象的greetTo()方法已经织入了切面类所定义的增强逻辑了。
通过配置织入@AspectJ切面
虽然可以通过编程的方式织入切面,但一般情况下,我们还是使用Spring的配置自动完成创建代理织入切面的工作。
①目标Bean
②使用了@AspectJ注解的切面类
③自动代理创建器,自动将@AspectJ注解切面类织入到目标Bean中
AnnotationAwareAspectJAutoProxyCreator"/>
AnnotationAwareAspectJAutoProxyCreator能够将@AspectJ注解切面的自动织入到目标Bean中。
这里,PreGreetingAspect是使用了@AspectJ注解描述的切面类,而NaiveWaiter是匹配切点的目标类。
如果使用基于Schema的aop命名空间进行配置,事情就更简单了:
xmlversion="1.0"encoding="UTF-8"?
>
//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-2.0.xsd
http:
//www.springframework.org/schema/aop②
http:
//www.springframework.org/schema/aop/spring-aop-2.0.xsd">
aspectj-autoproxy/>③基于@AspectJ切面的驱动器
首先,在配置文件中引入aop命名空间,如①、②处所示,然后通过aop命名空间的aspectj-autoproxy/>声明自动为Spring容器中那些匹配@AspectJ切面的Bean创建代理,织入切面。
当然,Spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被aspectj-autoproxy/>隐藏起来了。
aspectj-autoproxy/>有一个proxy-target-class属性,默认为false,表示使用JDK动态代理织入增强,当配置为aspectj-autoproxyproxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。
不过即使proxy-target-class设置为false,如果目标类没有声明接口,则Spring将自动使用CGLib动态代理。
@AspectJ语法基础
@AspectJ使用JDK5.0注解和正规的AspectJ5的切点表达式语言描述切面,由于Spring只支持方法的连接点,所以Spring仅支持部分AspectJ的切点语言。
在这节时,我们将对AspectJ切点表达式语言进行必要的学习。
切点表达式函数
AspectJ5的切点表达式由关键字和操作参数组成,如execution(*greetTo(..))的切点表达式,“execute”为关键字,而“*greetTo(..)”为操作参数。
在这里,execute代表目标类执行某一方法,而“*greetTo(..)”是描述目标方法的匹配模式串,两者联合起来所表示的切点匹配目标类greetTo()方法的连接点。
为了描述方便,我们将execution()称作函数,而将匹配串“*greetTo(..)”称作函数的入参。
Spring支持9个@ApsectJ切点表达式函数,它们用不同的方式描述目标类的连接点,根据描述对象的不同,可以将它们大致分为4种类型:
方法切点函数:
通过描述目标类方法信息定义连接点;
方法入参切点函数:
通过描述目标类方法入参的信息定义连接点;
目标类切点函数:
通过描述目标类类型信息定义连接点;
代理类切点函数:
通过描述目标类的代理类的信息定义连接点;
这4种类型的切点函数,通过表1进行说明:
表1切点函数
类别
函数
入参
说明
方法切点函数
execution()
方法
匹配模式串
表示满足某一匹配模式的所有目标类方法连接点。
如execution(*greetTo(..))表示所有目标类中的greetTo()方法。
@annotation()
方法注
解类名
表示标注了特定注解的目标方法连接点。
如@annotation(com.baobaotao.anno.NeedTest)表示任何标注了@NeedTest注解的目标类方法。
方法入参切点函数
args()
类名
通过判别目标类方法运行时入参对象的类型定义指定连接点。
如args(com.baobaotao.Waiter)表示所有有且仅有一个按类型匹配于Waiter的入参的方法。
@args()
类型注
解类名
通过判别目标方法的运行时入参对象的类是否标注特定注解来指定连接点。
如@args(com.baobaotao.Monitorable)表示任何这样的一个目标方法:
它有一个入参且入参对象的类标注@Monitorable注解。
目标类切点函数
within()
类名匹配串
表示特定域下的所有连接点。
如within(com.baobaotao.service.*)表示com.baobaotao.service包中的所有连接点,也即包中所有类的所有方法,而within(com.baobaotao.service.*Service)表示在com.baobaotao.service包中,所有以Service结尾的类的所有连接点。
target()
类名
假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点。
如通过target(com.baobaotao.Waiter)定义的切点,Waiter、以及Waiter实现类NaiveWaiter中所有连接点都匹配该切点。
@within()
类型注解类名
假如目标类按类型匹配于某个类A,且类A标注了特定注解,则目标类的所有连接点匹配这个切点。
如@within(com.baobaotao.Monitorable)定义的切点,假如Waiter类标注了@Monitorable注解,则Waiter以及Waiter实现类NaiveWaiter类的所有连接点都匹配。
@target()
类型注解类名
目标类标注了特定注解,则目标类所有连接点匹配该切点。
如@target(com.baobaotao.Monitorable),假如NaiveWaiter标注了@Monitorable,则NaiveWaiter所有连接点匹配切点。
代理类切点函数
this()
类名
代理类按类型匹配于指定类,则被代理的目标类所有连接点匹配切点。
这个函数比较难理解,这里暂不举例,留待后面详解。
@AspectJ除上表中所列的函数外,还有call()、initialization()、preinitialization()、staticinitialization()、get()、set()、handler()、adviceexecution()、withincode()、cflow()、cflowbelow()、if()、@this()以及@withincode()等函数,这些函数在Spring中不能使用,否则会抛出IllegalArgumentException异常。
在不特别声明的情况下,本书中所讲@AspectJ函数均指表1中所列的函数。
的控制流。
After注解类拥有2个成员:
value:
该成员用于定义切点;
argNames:
如前所述。
@DeclareParents
引介增强,相当于IntroductionInterceptor,DeclareParents注解类拥有2个成员:
value:
该成员用于定义切点,它表示在哪个目标类上添加引介增强;
defaultImpl:
默认的接口实现类。
除引介增强外,其它增强都很容易理解,我们将在本文后续内容中统一讲述,但引介增强的使用比较特别,因为我们特别在下节中为其准备了一个实例。
引介增强用法
请看以下两个接口及其实现类,如图2所示:
图2Waiter和Seller
假设我们希望NaiveWaiter能够同时充当售货员的角色,即通过切面技术为NaiveWaiter新增Seller接口的实现。
我们可以使用@AspectJ的引介增强来实现这一功能。
代码清单3EnableSellerAspect
packagecom.baobaotao.aspectj.basic;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.DeclareParents;
importcom.baobaotao.Seller;
importcom.baobaotao.SmartSeller;
@Aspect
publicclassEnableSellerAspect{
①为NaiveWaiter添加接口实现
@DeclareParents(value="com.baobaotao.NaiveWaiter",
defaultImpl=SmartSeller.class)②默认的接口实现类
publicSellerseller;③要实现的目标接口
}
在EnableSellerAspect切面中,我们通过@DeclareParents为NaiveWaiter添加了一个需要实现的Seller接口,并指定其默认实现类为SmartSeller,然后通过切面技术将SmartSeller融合到NaiveWaiter中,这样NaiveWaiter就实现Seller接口了。
在Spring配置文件中配置好切面和NaiveWaiterBean:
aspectj-autoproxy/>
运行以下测试代码:
packagecom.baobaotao.aspectj.basic;
importorg.springframework.context.ApplicationContext;
importorg.springframework.context.support.ClassPathXmlApplicationContext;
importcom.baobaotao.Seller;
importcom.baobaotao.Waiter;
publicclassDeclaredParentsTest...{
publicstaticvoidmain(String[]args)...{
StringconfigPath="com/baobaotao/aspectj/basic/beans.xml";
ApplicationContextctx=newClassPathXmlApplicationContext(configPath);
Waiterwaiter=(Waiter)ctx.getBean("waiter");
waiter.greetTo("John");
Sellerseller=(Seller)waiter;①可以成功进行强制类型转换
seller.sell("Beer","John");
}
}
代码成功执行,并输出以下信息:
NaiveWaiter:
greettoJohn...
SmartSeller:
sellBeertoJohn...
可见,NaiveWaiter已经成功地新增了Seller接口的实现。
1
切点函数详解
切点函数是AspectJ表达式语言的核心,是使用@AspectJ进行切面定义的难点,本节我们通过具体实例对切点函数进行深入学习。
为了方便讲解,我们假设目标类包括以下7个类,这些目标类都位于com.baobaotao.*包中:
图3Waiter和Seller类图
这些类中,除了SmartSeller#showGoods()方法是protected外,其它的方法都是public。
@annotation()
@annotation表示标注了某个注解的所有方法。
我们通过一个实例说明@annotation()的用法,TestAspect定义了一个后置增强切面,该增强将应用到标注有NeedTest的目标方法中:
packagecom.baobaotao.aspectj.fun;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Before;
@Aspect
publicclassTestAspect{
@AfterReturning("@annotation(com.baobaotao.anno.NeedTest)")①后置增强切面
publicvoidneedTestFun(){
System.out.println("needTestFun()executed!
");
}
}
假设NaughtyWaiter#greetTo()方法标注了@NeedTest注解,而NaiveWaiter#greetTo()方法没有标注@NeedTest注解,如代码清单4所示:
代码清单4标注了NeedTest注解的NaughtyWaiter
packagecom.baobaotao;
importcom.baobaotao.anno.NeedTest;
publicclassNau