【第六章】 AOP 之 6.3 基於Schema的AOP
6.3 基於Schema的AOP
基於Schema的AOP從Spring2.0之後通過“aop”名稱空間來定義切面、切入點及宣告通知。
在Spring配置檔案中,所以AOP相關定義必須放在<aop:config>標籤下,該標籤下可以有<aop:pointcut>、<aop:advisor>、<aop:aspect>標籤,配置順序不可變。
- <aop:pointcut>:用來定義切入點,該切入點可以重用;
- <aop:advisor>:用來定義只有一個通知和一個切入點的切面;
- <aop:aspect>:用來定義切面,該切面可以包含多個切入點和通知,而且標籤內部的通知和切入點定義是無序的;和advisor的區別就在此,advisor只包含一個通知和一個切入點。
6.3.1 宣告切面
切面就是包含切入點和通知的物件,在Spring容器中將被定義為一個Bean,Schema方式的切面需要一個切面支援Bean,該支援Bean的欄位和方法提供了切面的狀態和行為資訊,並通過配置方式來指定切入點和通知實現。
切面使用<aop:aspect>標籤指定,ref屬性用來引用切面支援Bean。
切面支援Bean“aspectSupportBean”跟普通Bean完全一樣使用,切面使用“ref”屬性引用它。
6.3.2 宣告切入點
切入點在Spring中也是一個Bean,Bean定義方式可以有很三種方式:
1)在<aop:config>標籤下使用<aop:pointcut>宣告一個切入點Bean,該切入點可以被多個切面使用,對於需要共享使用的切入點最好使用該方式,該切入點使用id屬性指定Bean名字,在通知定義時使用pointcut-ref屬性通過該id引用切入點,expression屬性指定切入點表示式:
<aop:config> <aop:pointcut id="pointcut" expression="execution(* cn.javass..*.*(..))"/> <aop:aspect ref="aspectSupportBean"> <aop:before pointcut-ref="pointcut" method="before"/> </aop:aspect> </aop:config>
2)在<aop:aspect>標籤下使用<aop:pointcut>宣告一個切入點Bean,該切入點可以被多個切面使用,但一般該切入點只被該切面使用,當然也可以被其他切面使用,但最好不要那樣使用,該切入點使用id屬性指定Bean名字,在通知定義時使用pointcut-ref屬性通過該id引用切入點,expression屬性指定切入點表示式:
<aop:config>
<aop:aspect ref="aspectSupportBean">
<aop:pointcut id=" pointcut" expression="execution(* cn.javass..*.*(..))"/>
<aop:before pointcut-ref="pointcut" method="before"/>
</aop:aspect>
</aop:config>
3)匿名切入點Bean,可以在宣告通知時通過pointcut屬性指定切入點表示式,該切入點是匿名切入點,只被該通知使用:
<aop:config>
<aop:aspect ref="aspectSupportBean">
<aop:after pointcut="execution(* cn.javass..*.*(..))" method="afterFinallyAdvice"/>
</aop:aspect>
</aop:config>
6.3.3 宣告通知
基於Schema方式支援前邊介紹的5中通知型別:
一、前置通知:在切入點選擇的方法之前執行,通過<aop:aspect>標籤下的<aop:before>標籤宣告:
<aop:before pointcut="切入點表示式" pointcut-ref="切入點Bean引用"
method="前置通知實現方法名"
arg-names="前置通知實現方法引數列表引數名字"/>
pointcut和pointcut-ref:二者選一,指定切入點;
method:指定前置通知實現方法名,如果是多型需要加上引數型別,多個用“,”隔開,如beforeAdvice(java.lang.String);
arg-names:指定通知實現方法的引數名字,多個用“,”分隔,可選,類似於【3.1.2 構造器注入】中的引數名注入限制:在class檔案中沒生成變數除錯資訊是獲取不到方法引數名字的,因此只有在類沒生成變數除錯資訊時才需要使用arg-names屬性來指定引數名,如arg-names="param"表示通知實現方法的引數列表的第一個引數名字為“param”。
首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:
public void sayBefore(String param);
其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現
@Override
public void sayBefore(String param) {
System.out.println("============say " + param);
}
第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:
public void beforeAdvice(String param) {
System.out.println("===========before advice param:" + param);
}
最後在chapter6/advice.xml配置檔案中進行如下配置:
<bean id="helloWorldService" class="cn.javass.spring.chapter6.service.impl.HelloWorldService"/>
<bean id="aspect" class="cn.javass.spring.chapter6.aop.HelloWorldAspect"/>
<aop:config>
<aop:aspect ref="aspect">
<aop:before pointcut="execution(* cn.javass..*.sayBefore(..)) and args(param)"
method="beforeAdvice(java.lang.String)"
arg-names="param"/>
</aop:aspect>
</aop:config>
測試程式碼cn.javass.spring.chapter6.AopTest:
@Test
public void testSchemaBeforeAdvice(){
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayBefore("before");
System.out.println("======================================");
}
將輸入:
|
分析一下吧:
1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayBefore(..)) ”匹配目標方法sayBefore,且使用“args(param)”匹配目標方法只有一個引數且傳入的引數型別為通知實現方法中同名的引數型別;
2)目標方法定義:使用method=" beforeAdvice(java.lang.String) "指定前置通知實現方法,且該通知有一個引數型別為java.lang.String引數;
3)目標方法引數命名:其中使用arg-names=" param "指定通知實現方法引數名為“param”,切入點中使用“args(param)”匹配的目標方法引數將自動傳遞給通知實現方法同名引數。
二、後置返回通知:在切入點選擇的方法正常返回時執行,通過<aop:aspect>標籤下的<aop:after-returning>標籤宣告:
<aop:after-returning pointcut="切入點表示式" pointcut-ref="切入點Bean引用"
method="後置返回通知實現方法名"
arg-names="後置返回通知實現方法引數列表引數名字"
returning="返回值對應的後置返回通知實現方法引數名"
/>
pointcut和pointcut-ref:同前置通知同義;
method:同前置通知同義;
arg-names:同前置通知同義;
returning:定義一個名字,該名字用於匹配通知實現方法的一個引數名,當目標方法執行正常返回後,將把目標方法返回值傳給通知方法;returning限定了只有目標方法返回值匹配與通知方法相應引數型別時才能執行後置返回通知,否則不執行,對於returning對應的通知方法引數為Object型別將匹配任何目標返回值。
首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:
public boolean sayAfterReturning();
其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現
@Override
public boolean sayAfterReturning() {
System.out.println("============after returning");
return true;
}
第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:
public void afterReturningAdvice(Object retVal) {
System.out.println("===========after returning advice retVal:" + retVal);
}
最後在chapter6/advice.xml配置檔案中接著前置通知配置的例子新增如下配置:
<aop:after-returning pointcut="execution(* cn.javass..*.sayAfterReturning(..))"
method="afterReturningAdvice"
arg-names="retVal"
returning="retVal"/>
測試程式碼cn.javass.spring.chapter6.AopTest:
@Test
public void testSchemaAfterReturningAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAfterReturning();
System.out.println("======================================");
}
將輸入:
|
分析一下吧:
1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayAfterReturning(..)) ”匹配目標方法sayAfterReturning,該方法返回true;
2)目標方法定義:使用method="afterReturningAdvice"指定後置返回通知實現方法;
3)目標方法引數命名:其中使用arg-names="retVal"指定通知實現方法引數名為“retVal”;
4)返回值命名:returning="retVal"用於將目標返回值賦值給通知實現方法引數名為“retVal”的引數上。
三、後置異常通知:在切入點選擇的方法丟擲異常時執行,通過<aop:aspect>標籤下的<aop:after-throwing>標籤宣告:
<aop:after-throwing pointcut="切入點表示式" pointcut-ref="切入點Bean引用"
method="後置異常通知實現方法名"
arg-names="後置異常通知實現方法引數列表引數名字"
throwing="將丟擲的異常賦值給的通知實現方法引數名"/>
pointcut和pointcut-ref:同前置通知同義;
method:同前置通知同義;
arg-names:同前置通知同義;
throwing:定義一個名字,該名字用於匹配通知實現方法的一個引數名,當目標方法丟擲異常返回後,將把目標方法丟擲的異常傳給通知方法;throwing限定了只有目標方法丟擲的異常匹配與通知方法相應引數異常型別時才能執行後置異常通知,否則不執行,對於throwing對應的通知方法引數為Throwable型別將匹配任何異常。
首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:
public void sayAfterThrowing();
其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現
@Override
public void sayAfterThrowing() {
System.out.println("============before throwing");
throw new RuntimeException();
}
第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:
public void afterThrowingAdvice(Exception exception) {
System.out.println("===========after throwing advice exception:" + exception);
}
最後在chapter6/advice.xml配置檔案中接著前置通知配置的例子新增如下配置:
<aop:after-throwing pointcut="execution(* cn.javass..*.sayAfterThrowing(..))"
method="afterThrowingAdvice"
arg-names="exception"
throwing="exception"/>
測試程式碼cn.javass.spring.chapter6.AopTest:
@Test(expected = RuntimeException.class)
public void testSchemaAfterThrowingAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAfterThrowing();
System.out.println("======================================");
}
將輸出:
|
分析一下吧:
1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayAfterThrowing(..))”匹配目標方法sayAfterThrowing,該方法將丟擲RuntimeException異常;
2)目標方法定義:使用method="afterThrowingAdvice"指定後置異常通知實現方法;
3)目標方法引數命名:其中使用arg-names="exception"指定通知實現方法引數名為“exception”;
4)異常命名:returning="exception"用於將目標方法丟擲的異常賦值給通知實現方法引數名為“exception”的引數上。
四、後置最終通知:在切入點選擇的方法返回時執行,不管是正常返回還是丟擲異常都執行,通過<aop:aspect>標籤下的<aop:after >標籤宣告:
<aop:after pointcut="切入點表示式" pointcut-ref="切入點Bean引用"
method="後置最終通知實現方法名"
arg-names="後置最終通知實現方法引數列表引數名字"/>
pointcut和pointcut-ref:同前置通知同義;
method:同前置通知同義;
arg-names:同前置通知同義;
首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:
public boolean sayAfterFinally();
其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現
@Override
public boolean sayAfterFinally() {
System.out.println("============before finally");
throw new RuntimeException();
}
第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
最後在chapter6/advice.xml配置檔案中接著前置通知配置的例子新增如下配置:
<aop:after pointcut="execution(* cn.javass..*.sayAfterFinally(..))"
method="afterFinallyAdvice"/>
測試程式碼cn.javass.spring.chapter6.AopTest:
@Test(expected = RuntimeException.class)
public void testSchemaAfterFinallyAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAfterFinally();
System.out.println("======================================");
}
將輸入:
|
分析一下吧:
1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayAfterFinally(..))”匹配目標方法sayAfterFinally,該方法將丟擲RuntimeException異常;
2)目標方法定義:使用method=" afterFinallyAdvice "指定後置最終通知實現方法。
五、環繞通知:環繞著在切入點選擇的連線點處的方法所執行的通知,環繞通知非常強大,可以決定目標方法是否執行,什麼時候執行,執行時是否需要替換方法引數,執行完畢是否需要替換返回值,可通過<aop:aspect>標籤下的<aop:around >標籤宣告:
<aop:around pointcut="切入點表示式" pointcut-ref="切入點Bean引用"
method="環繞通知實現方法名" />
pointcut和pointcut-ref:同前置通知同義;
method:同前置通知同義;
環繞通知第一個引數必須是org.aspectj.lang.ProceedingJoinPoint型別,在通知實現方法內部使用ProceedingJoinPoint的proceed()方法使目標方法執行,proceed 方法可以傳入可選的Object[]陣列,該陣列的值將被作為目標方法執行時的引數。
public void sayAround(String param);
其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現
@Override
public void sayAround(String param) {
System.out.println("============around param:" + param);
}
第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("===========around before advice");
Object retVal = pjp.proceed(new Object[] {"replace"});
System.out.println("===========around after advice");
return retVal;
}
最後在chapter6/advice.xml配置檔案中接著前置通知配置的例子新增如下配置:
<aop:around pointcut="execution(* cn.javass..*.sayAround(..))"
method="aroundAdvice"/>
測試程式碼cn.javass.spring.chapter6.AopTest:
@Test
public void testSchemaAroundAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml");
IHelloWorldService helloworldService =
ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAround("haha");
System.out.println("======================================");
}
將輸入:
|
分析一下吧:
1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayAround(..))”匹配目標方法sayAround;
2)目標方法定義:使用method="aroundAdvice"指定環繞通知實現方法,在該實現中,第一個方法引數為pjp,型別為ProceedingJoinPoint,其中“Object retVal = pjp.proceed(new Object[] {"replace"});”,用於執行目標方法,且目標方法引數被“new Object[] {"replace"}”替換,最後返回“retVal ”返回值。
3)測試:我們使用“helloworldService.sayAround("haha");”傳入引數為“haha”,但最終輸出為“replace”,說明引數被替換了。