一、@AspectJ语法基础
1.1 切点表达式函数
类名类别 | 函数 | 入参 |
方法切点函数 | execution() | 方法匹配模式串 |
@annotation() | 方法注解类名 | |
方法入参切点函数 | args() | 类名 |
@args() | 类型注解类名 | |
目标类切点函数 | within() | 类名匹配串 |
target() | 类名 | |
@within() | 类型注解类名 | |
@target() | 类型注解类名 | |
代理类切点函数 | this() |
1.2 在函数入参中使用通配符
*:匹配任意字符,但它只能匹配上下文中的一个元素
..:匹配任意字符,可以匹配上下文中的多个元素。但在表示类实,必须和*联合使用,在表示入参时可以单独使用。
+:表示按类型匹配指定类的所有类,必须跟在类名后面,继承或扩展指定类的所有类,同时还包括指定类本身。
其中exection()和within()支持所有通配符。args()、this()和target()仅支持+。而@args()、@within()、@target()和@annotation不支持通配符。
1.3 增强类型
@Before 前置增强,拥有两个成员:value用于定义切点,argNames指定注解所标准增强方法的参数名,多个参数名用逗号分隔。
@AfterReturning 后置增强,拥有四个成员:value用于定义切点,pointcut表示切点信息,显时定义将覆盖value值,returning将目标对象方法的返回值绑定给增强的方法,argNames同上。
@Around 环绕增强,value定义切点,argNames。
@AfterThrowing 抛出异常增强,value定义切点,pointcut表示切点信息,throwing将抛出的异常绑定到增强方法中,argNames。
@After Final增强,不管抛出异常还是正常退出,该增强都会等到执行。value定义切点,argNames。
@DeclareParents 引介增强,value定义切点,表示在那个目标类上添加引介增强;defaultImpl默认的接口实现类。
1.4 切点函数详解
1.4.1 @annotation()
表示标注了某个注解的所有方法。
@Aspect //标识为一个切面
public class TestAspect {
@AfterReturning("@annotation(aspectj.NeedTest)") //后置增强切面
public void needTestFun(){
System.out.println("needTestFun() executed!");
}
}
public class NaughtyWaiter implements Waiter {
@NeedTest
public void greetTo(String name) {
System.out.println("naughty waiter is greeting to,Mr."+name);
}
...
}
//xml配置
<aop:aspectj-autoproxy/>
<bean id="naiveWaiter" class="aop.beforeadvice.NaiveWaiter"/> <bean id="naughtyWaiter" class="aspectj.NaughtyWaiter"/>
<bean class="aspectj.TestAspect"/>
//测试方法
Waiter naiveWaiter=(Waiter)context.getBean("naiveWaiter");
Waiter naughtWaiter=(Waiter)context.getBean("naughtyWaiter");
naiveWaiter.greetTo("John");
naughtWaiter.greetTo("John");
//结果
waiter greeting to,Mr.John
naughty waiter is greeting to,Mr.John
needTestFun() executed! //后置增强代码
1.4.2 execution()
语法格式:execution(<修饰符格式>? <返回类型模式> <方法名模式> (参数模式) <异常模式>?)
1.通过方法名签名定义切点
如:execution(public * *(..))匹配所有目标类的public方法。
2.通过类定义切点
如:execution(* com.smart.Waiter+.*(..))匹配Waiter接口及其所有实现类的方法。
3.通过类包定义切点
在类名模式串中,.* 表示包下所有的类,..* 表示包、子包下的所有类。
如:execution(* com..* .* Dao.find*(..))匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名前缀是find。
4.通过方法入参定义切点
如:execution(* joke(String,*))匹配目标类中的joke方法,第一个入参是String,第二个入参可以是任意类型,但joke(String,double,String)不匹配。即只能有两个参数。
execution(* joke(String,..))第一个参数是String,后面可以有任意个入参且入参类型不限,如joke(String)、joke(String,int)、joke(String,double,String)都匹配。
execution(* joke(Object))只能匹配joke(Object object)不能匹配joke(String)或joke(Client)。而execution(* joke(Object+))匹配Object类型或者其子类,它可以匹配joke(String)。
1.4.3 args()和@args()
args():接收一个类名,表示目标类方法入参对象是指定类(包括子类)时,切点匹配。如args(com.smart.Waiter)表示运行时入参是Waiter类型的方法。它等价与execution(* *(com.smart.Waiter+)),也等价于args(com.smart.Waiter+)。
@args():接收一个注解类的类名,当方法的运行时入参对象标注了指定的注解时,匹配切点。如果在类继承树中注解点高于入参类型点,则该方法不可能匹配切点@args(M),如果在类继承树中注解点低于入参类型点,则该注解所在类及其子孙类作为方法入参时,该方法匹配切点@args(M)。
1.4.4 within()
within()函数定义的连接点是针对目标类而言的,连接点的最小范围只能是类。语法格式为within(<类匹配模式>)。
within(com.smart.*)匹配com.smart包下的所有类,但不包括子孙包。
within(com.smart..*)匹配com.smart包及子孙包中的类。
1.4.5 @within()和@target()
@target(M)只匹配标注了@M的目标类,@within(M)匹配标注了@M的类及子孙类。
1.4.6 target()和this()
target(M)表示如果目标类按类型匹配与M,则目标类的所有方法都匹配切点。
this()判断代理对象的类是否按类型匹配于指定类,如果匹配,则代理对象的所有连接点匹配切点。
二、@AspectJ进阶
2.1 切点复合运算
@After(“within(com.smart.*) && execution( * greetTo(..))”)匹配com.smart包中所有greetTo()方法的切点。
@Before(“!target(com.smart.NaiveWaiter) && execution( * serveTo(..))”)匹配所有serveTo方法并且方法不位于NaiveWaiter目标类的切点。
@AfterReturning(“target(com.smart.Waiter) || target(com.smart.Seller)”)匹配Waiter和Seller接口实现类所有连接点的切点。
2.2 命名切点 @Pointcut
public class TestNamePointcut{
@Pointcut("within(com.smart.*)")//通过注解方法inPackage()对该切点进行命名,访问符为private,表明该命名切点只能在本切面类中使用
private void inPackage(){
}
@Pointcut("execution(* greetTo(..))")//该切点可以在当前包的切面类、子切面类中使用
protected void greetTo(){
}
@Pointcut("inPackage() and greetTo()")//引用民名切点定义的切点,本切点也是命名切点,对应的可视域是public
public void inPkgGreetTo(){
}
}
@Aspect
public class TestAspect{
@Before("TestNamePointcut.inPkgGreetTo()")//引用TestNamePointcut.inPkgGreetTo()切点
public void pkgGreetTo(){
}
}
2.3 增强织入的顺序
一个连接同时匹配多个切点,增强在连接点的织入顺序如下:
如果增强在同一个切面中声明,则依照增强在切面类中定义的顺序进行织入。
如果增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Ordered借口,则由接口方法的顺序号决定(顺序号小的先织入)。
如果增强位于不同的切面类中,且这些切面类没有实现org.springframework.core.Ordered接口,则织入的顺序是不确定的。
2.4 访问连接点信息
AspectJ使用org.aspectj.lang.JointPoint接口表示目标类连接点对象。如果是环绕增强,则使用org.aspectj.lang.ProceeingJointPoint表示连接点对象,该类是JointPoint的子接口。
@Aspect
public class TestAspect {
@Around("execution(* greetTo(..)) && target(aop.beforeadvice.NaiveWaiter)")
public void jointPointAccess(ProceedingJoinPoint pjp) throws Throwable{//声明连接点入参
System.out.println("-----------JoinpontAccess----------");
System.out.println("Args[0]"+pjp.getArgs()[0]);
System.out.println("signature:"+pjp.getTarget().getClass());//访问连接点信息
pjp.proceed();//通过连接点执行目标对象方法
System.out.println("-------------Join point access----------");
}
}
//测试代码
Waiter naiveWaiter=(Waiter)context.getBean("naiveWaiter");
naiveWaiter.greetTo("John");
//结果
-----------JoinpontAccess----------
Args[0]John
signature:class aop.beforeadvice.NaiveWaiter
waiter greeting to,Mr.John
-------------Join point access----------
2.5 绑定连接点方法入参
@Aspect
public class TestAspect {
@Before("target(aop.beforeadvice.NaiveWaiter) && args(name,num,..)")
//先通过名字匹配,确定第一个入参为String,第二个入参为int;再通过类型匹配,匹配到NaiveWaiter#smile(String,int)方法
public void bindJoinPointParams(int num,String name){
System.out.println("---------bindJoinPointParams()------");
System.out.println("name:"+name);
System.out.println("num:"+num);
System.out.println("-----------bindJointPointParams()------");
}
}
//NaivaWaiter.java
public class NaiveWaiter implements Waiter{
public void smile(String name,int times){
System.out.println("NaiveWaiter smile to Mr."+name+" "+times+"times");
}
}
//xml配置
<aop:aspectj-autoproxy proxy-target-class="true"/>//默认使用jdk代理,这里需要使用cglib代理
<bean id="naiveWaiter" class="aop.beforeadvice.NaiveWaiter"/>
<bean id="naughtyWaiter" class="aspectj.NaughtyWaiter"/>
<bean class="aspectj.TestAspect"/>
//测试
NaiveWaiter naiveWaiter=(NaiveWaiter)context.getBean("naiveWaiter");
naiveWaiter.smile("John",2);
//结果
---------bindJoinPointParams()------
name:John
num:2
-----------bindJointPointParams()------
NaiveWaiter smile to Mr.John 2times
2.6 绑定代理对象
使用this()和target()函数可绑定被代理对象实例。
@Aspect
public class TestAspect {
@Before("this(waiter)")//通过入参waiter查找出对应类型为Waiter,因而切点的表达式为this(Waiter)。当增强方法织入目标连接点时,增强方法通过waiter入参绑定目标对象。
public void bindProxyobj(Waiter waiter){
System.out.println("------------");
System.out.println(waiter.getClass().getName());
System.out.println("------------");
}
}
//结果
------------
aop.beforeadvice.NaiveWaiter$$EnhancerBySpringCGLIB$$38f1b781
------------
waiter greeting to,Mr.John
2.7 绑定类注解对象
@within()和@target()函数可以将目标类的注解对象绑定到增强方法中。用法同this()函数
2.8 绑定返回值
在后置增强中,可以通过returning绑定连接点方法的返回值。
@AfterReturning(value="target(com.smart.NaiveWaiter)",returning="retVal")
public void bindReturnValue(int retVal){
System.out.println(""return value:"+retVal);
}
2.9 绑定抛出的异常
连接点抛出的异常必须使用AfterThrowing注解的throwing成员进行绑定。
@AfterThrowing(value="target(com.smart.SmartSeller)",throwing="iae")
public void bingExeception(IllegalArgumentException iae){
System.out.println("exeception:"+iae.getMessage());
}
三、基于schema配置切面
3.1 配置命名切点
<bean id="adviceMethod" class="aspectj.TestAspect"/> //增强方法所在的Bean
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethod">
<aop:pointcut id="greetToPointcut" expression="target(aop.beforeadvice.NaiveWaiter) and execution(* greetTo(..))"/> //定义切点,声明切点表达式
<aop:before method="bindJoinPointParams" pointcut-ref="greetToPointcut"/> //定义增强类型,增强方法,引用切点
</aop:aspect>
</aop:config>
3.2 各种增强类型的配置
3.2.1 后置增强
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethod">
<aop:after-returning method="afterReturning" pointcut-ref="greetToPointcut" returning="retVal"/> //定义增强类型,增强方法,引用切点
</aop:aspect>
</aop:config>
public class AdviceMethod{
public void afterReturning(int retVal){//增强方法,retVal和配置文件中的returning属性值相同。
}
}
3.2.2 环绕增强
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethod">
<aop:around method="aroundMethod" pointcut-ref="greetToPointcut"/> //定义增强类型,增强方法,引用切点
</aop:aspect>
</aop:config>
public class AdviceMethod{
public void aroundMethod(ProceedingJoinPoint pjp){//增强方法,pjp可以访问到环绕增强的连接点信息。
}
}
3.2.3 抛出异常增强
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethod">
<aop:after-throwing method="afterThrowingMethod" pointcut="target(com.smart.SmartSeller) and execution(* checkBill(..))" throwing="iae"/> //定义增强类型,增强方法,引用切点
</aop:aspect>
</aop:config>
public class AdviceMethod{
public void afterThrowingMethod(IllegalArgumentException iae){//增强方法
}
}
3.2.4 Final增强
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethod">
<aop:after method="afterMethod" pointcut="target(com.smart.SmartSeller) and execution(* checkBill(..))" throwing="iae"/> //定义增强类型,增强方法,引用切点
</aop:aspect>
</aop:config>
public class AdviceMethod{
public void afterMethod(){//增强方法
}
}
3.2.5 引介增强
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethod">
<aop:declare-parents implement-interface="com.smart.Seller" default-impl="com.smart.SmartSeller" types-matching="com.smart.Waiter+"/> //定义增强类型,增强方法,引用切点
</aop:aspect>
</aop:config>
public class AdviceMethod{
public void afterThrowingMethod(IllegalArgumentException iae){//增强方法
}
}
3.3 绑定连接点信息
所有增强类型对应的方法的第一个入参都可以声明为JoinPoint(环绕增强可以声明为ProceedingJoinPoint)访问连接点信息。
<aop:after-returning>可以通过returning属性绑定连接点方法的返回值。<aop:after-throwing>可以通过throwing属性绑定连接点方法所抛出的异常。
所有增强类型都可以通过可绑定参数的切点函数绑定连接点方法的入参。
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethod">
<aop:after method="afterMethod" pointcut="target(com.smart.SmartSeller) and args(name,num,..)" throwing="iae"/> //定义增强类型,增强方法,引用切点,函数命名同@AspectJ命名相同
</aop:aspect>
</aop:config>
3.4 Advisor配置
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="testAdvice" pointcut="execution(* com..*.Waiter.greetTo(..))" />
</aop:config> //advisor仅包含一个切点和一个增强
<bean id="testAdvice" class="com.smart.schema.TestBeforeAdvice"/>
public class TestBeforeAdvice implements MethodBeforeAdvice{
public void before(Method method,Object [] args,Object target){
}
}
四、各种切面类型总结
@AspectJ | <aop:aspect> | Advisor | <aop:advisor> | ||
增强类型 | 前置增强 | @Before | <aop:before> | MethodBeforeAdvice | 同Advisor |
后置增强 | @AfterReturning | <aop:after-returning> | AfterReturningAdvice | 同Advisor | |
环绕增强 | @Around | <aop-around> | MethodInterceptor | 同Advisor | |
抛出异常增强 | @AfterThrowing | <aop:after-throwing> | ThrowsAdvice | 同Advisor | |
final增强 | @After | <aop:after | 无对应接口 | 同Advisor | |
引介增强 | @DecaleParents | <aop:declare-parents> | IntroductionInterceptor | 同Advisor | |
切点定义 | 支持AspectJ切点表达式语法,可以通过@Pointcut注解定义命名切点 | 支持AspectJ切点表达式语法,可以通过<aop:pointcut>命名切点 | 直接通过基于Pointcut的实现类定义切点 | 基本上同<aop:aspect>相同,不过切点函数不能绑定参数 | |
连接点方法入参绑定 | 使用JoinPoint、ProceedingJoinPoint连接点对象; 使用切点函数指定参数名绑定 | 同@AspectJ<aop:after-returning> | 通过增强接口方法入参绑定 | 同Advisor | |
连接点方法返回值或抛出异常绑定 | 在后置增强中,使用@AfterReturning的returning成员绑定方法返回值; 在抛出异常增强中,使用@AfterThrowing的throwing成员绑定方法抛出的异常 | 在后置增强中,使用<aop:after-returning>的returning属性绑定方法返回值 在抛出异常增强中,使用<aop:after-throwing>的throwing属性绑定方法抛出的异常 | 通过增强接口方法入参绑定 | 蓉Advisor |