1、什麼是AOP?概念和簡單示例講解。
2、Spring中AOP基礎部分。
3、Spring中AOP使用升級篇。
AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程(也叫面向方面),可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。 -- 摘自百度知道
舉個例子:
好比說“雷政富”大哥是一個對象,他有“腰部運動”的方法(當然腰部運動方法接收的參數是一個美女對象),這大哥的“腰部運動”方法就是單純的生理需求。
可是開發商很陰險,不僅給雷大哥提供了“腰部運動”的“道具”,還在雷大哥不知情的情況下,在“腰部運動”方法前後又加了“錄像”功能。
當然,前提是雷大哥的“腰部運動”是在開發商提供的“特殊賓館”裏進行的。
這種藉助特殊的“賓館”,來完成在已有“腰部運動”方法前後,添加額外的“錄像”功能的方式,稱之爲 AOP。
適用範圍:
1、無法修改原對象;(你讓雷哥自己拍了給你,你不找死麼?)
2、需要重複修改大量對象,出現大量重複工作;(不是還有5個麼?)
AOP的核心概念:
連接點(jointpoint):一個連接點是一個程序執行過程中的特定點。用來定義在程序的什麼地方能通過AOP加入額外的邏輯。(“腰部運動”方法的執行位置 -- 趴上牀那個點)
通知(advice):在某一特定的連接點處運行的代碼稱爲“通知”。(即“錄像”)
切入點(pointcut):切入點是用來定義某一個通知該何時執行的一組連接點。(具體誰開啓哪個房間的時候開啓錄像 -- 因爲有時候,有倆大叔無意開了這個房間,你也要看麼?)
方面(aspect):通知和切入點的組合叫做方面。這個組合定義了一段程序中應該包含的邏輯以及何時應該執行該邏輯。(就是一整套整合流程,人家演練了好幾次呢)
織入(weaving):織入是將方面真正的加入程序代碼的過程。(開機、錄製過程)
目標(target):如果一個對象的執行過程受到某個AOP操作的修改,那麼他就叫做一個目標對象,也成爲通知對象。(雷政富)
引入(introduction):通過引入,我們可以在一個對象中加入新的方法或者字段,以改變他的結構。(帶到了指定包房)
我們來完成我們上面的例子(通過JDK 的動態代理):
首先我們要明確一點,“腰部運動”要有指定接口,爲什麼呢?可想而知,人家提前演練總要有規範流程的。
package com.partner4java.demo1;
/**
* 娛樂項目
*
* @author partner4java
*
*/
public interface Entertainment {
/**
* 要不運動
*
* @param girl
* 訓練有素的神祕女孩
* @return 返回運動時間,單位秒
*/
public int loinMove(String girl);
}
package com.partner4java.demo1;
/**
* 不要羨慕雷哥,這也是體力勞動,很累的,特別是每天都有那麼多科目
*
* @author partner4java
*
*/
public class LeiZhengFu implements Entertainment {
public int loinMove(String girl) {
System.out.println("雷哥脫光了衣服,露出了傲人的身材");
System.out.println("爬上了:" + girl);
// 12秒哥的由來
return 12;
}
}
package com.partner4java.demo1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 房間類: 我們創辦了一個特殊的房間,專門用於偷拍
* (本類特點,只是接收了Entertainment類型參數,所以,另外5個人也不需要再單獨建立房間(InvocationHandler),只需要讓開發商領進來(Proxy)就可以了)
* @author partner4java
*
*/
public class GuestHouse implements InvocationHandler {
private Entertainment entertainment;
public Object bind(Entertainment entertainment) {
this.entertainment = entertainment;
// 領進來特殊賓館的特殊房間,讓你去做“腰部運動”,其實這哥們並不知道這房間都對他偷偷做了什麼,但是偷偷做的動作還是需要依賴於這哥們
return Proxy.newProxyInstance(
entertainment.getClass().getClassLoader(), entertainment
.getClass().getInterfaces(), this);
}
// 這就是房間的步驟
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("開拍");
//雷哥做腰部運動
Object returnO = method.invoke(entertainment, args);
System.out.println("關機");
System.out.println("拍攝時間:" + returnO + "秒");
System.out.println("真你媽浪費資源,不行讓哥來啊");
return returnO;
}
}
執行下面三行代碼:
誕生了12秒哥的神奇錄像
GuestHouse guestHouse = new GuestHouse();
Entertainment leiGe = (Entertainment) guestHouse.bind(new LeiZhengFu());
leiGe.loinMove("神祕女孩");
Demo代碼大體解釋:
用到了兩個類Proxy、InvocationHandler。
Proxy:
Proxy 提供用於創建動態代理類和實例的靜態方法,它還是由這些方法創建的所有動態代理類的超類。
(也就是那個開發商,他安排了整套計劃 -- 比如帶着雷哥去指定房價)
newProxyInstance方法:
返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。
參數:
loader - 定義代理類的類加載器
interfaces - 代理類要實現的接口列表
h - 指派方法調用的調用處理程序
InvocationHandler:
InvocationHandler 是代理實例的調用處理程序 實現的接口。
每個代理實例都具有一個關聯的調用處理程序。對代理實例調用方法時,將對方法調用進行編碼並將其指派到它的調用處理程序的 invoke 方法。
(就是佈置好的那個房間,具體拍照工作都在這進行的)
invoke方法:
在代理實例上處理方法調用並返回結果。在與方法關聯的代理實例上調用方法時,將在調用處理程序上調用此方法。
參數:
proxy - 在其上調用方法的代理實例
method - 對應於在代理實例上調用的接口方法的 Method 實例。Method 對象的聲明類將是在其中聲明方法的接口,該接口可以是代理類賴以繼承方法的代理接口的超接口。
args - 包含傳入代理實例上方法調用的參數值的對象數組,如果接口方法不使用參數,則爲 null。基本類型的參數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的實例中。
=================================================================
再舉倆Demo:
兩種類型AOP:靜態AOP和動態AOP。
兩者的區別在於何時真正發生織入過程,以及如何織入。
靜態AOP:
我們在一個靜態AOP實現中通過修改應用程序實際字節碼來完成織入過程,從而根絕需要修改和擴展程序代碼。
顯然,這是個達到織入過程的高性能方式,因爲最終結果就是普通的Java字節碼,在運行時我們不再需要特別的技巧來確定什麼時候應該執行通知。
(AspectJ便是靜態AOP實現的一個絕好的示例。)
demo:
代理對象與被代理對象必須實現同一個接口。
package cn.partner4java.proxy.staticproxy;
/**
* 靜態代理,統一接口
* @author partner4java
*
*/
public interface IHello {
/**
* 可以帶來的統一方法
* @param name
*/
public void hello(String name);
}
package cn.partner4java.proxy.staticproxy;
/**
* 被代理的對象,需要藉助代理對象加入日誌
* @author partner4java
*
*/
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello " + name);
}
}
package cn.partner4java.proxy.staticproxy;
/**
* 代理對象,給被代理對象添加日誌
*/
public class HelloProxy implements IHello {
private IHello iHello;
public HelloProxy(IHello iHello) {
super();
this.iHello = iHello;
}
public void hello(String name) {
System.out.println("記錄日誌");
iHello.hello(name);
}
}
package cn.partner4java.proxy.staticproxy;
/**
* 調用
* @author partner4java
*
*/
public class ProxyDemo {
public static void main(String[] args) {
IHello iHello = new HelloProxy(new HelloSpeaker());
iHello.hello("long");
}
}
動態AOP:
動態代理區別於靜態帶來實現的地方在於織入過程是在運行時動態進行的。
動態AOP實現的主要好處在於,你能輕易的修改一個應用的整個方面的集合而無需重新編譯主程序的代碼。
例子:
自己實現一般實現java.lang.reflect.InvocationHandler接口。
package cn.partner4java.proxy.dynamicproxy;
public interface IHello {
public void hello(String name);
}
package cn.partner4java.proxy.dynamicproxy;
/**
* 被代理的對象,需要藉助代理對象加入日誌
* @author partner4java
*
*/
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello " + name);
}
}
package cn.partner4java.proxy.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 動態代理對象
* @author partner4java
*
*/
public class LogHandler implements InvocationHandler {
private Object delegate;
public Object bind(Object delegate){
this.delegate = delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(), this);
}
/**
* 代理對象,這裏面還可以改變原有的方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
try {
System.out.println("添加日誌");
result = method.invoke(delegate, args);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package cn.partner4java.proxy.dynamicproxy;
/**
* 測試
* @author partner4java
*
*/
public class ProxyDemo {
public static void main(String[] args) {
LogHandler logHandler = new LogHandler();
IHello iHello = (IHello) logHandler.bind(new HelloSpeaker());
iHello.hello("long");
}
}
=================================================================
第二部分:Spring中AOP基礎部分
Spring AOP實際上是所有AOP特性集合的一個子集,他只提供了諸如AspectJ等其他AOP實現中的功能的一小部分。
不過,正因爲他放棄了一些不常用的功能,才使用如此簡單,而簡單是Spring AOP最強大的地方之一。
aopalliance AOP聯盟:是一個包含Spring在內的很多開源AOP項目的代表們所組成的聯合組織,他定義了一個AOP實現接口的標準集。
本章學習主要分爲了三部分:
本章第一部分:HelloWorld Spring AOP -- 藉助Spring封裝類來完成AOP
(我們就不再用雷大哥舉例子了,免得有上門查水錶的)
需求:
public class MessageWriter {
public void wirteMessage(){
System.out.println("World");
}
}
在World的打印前後加上文字如:"Hello World!"。也就是在前面加了“Hello”,在後面加了“!”。
用AOP術語來說,我們需要一個包圍通知(around advice),也就是一個包圍連接點的通知。
實現通過兩小步:
1、實現包圍通知:
實現MethodInterceptor接口:
MethodInterceptor接口是對方法調用連接點實現包圍通知的AOP聯盟標準接口。
MethodInterceptor對象代表當前被通知的方法調用,我們使用這個類來控制具體什麼時候進行方法調用。
public class MessageDecorator implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Hello ");
//調用原方法獲取返回值
Object retVal = invocation.proceed();
System.out.println(" !");
return retVal;
}
}
2、將MessageDecorator通知織入代碼中:
我們新建一個MessageWriter對象,即目標對象,然後爲其創建一個代理,並讓代理工廠(proxy factory)織入MessageDecorator通知。
public class HelloWorldWeaver {
public static void main(String[] args) {
MessageWriter messageWriter = new MessageWriter();
//create the proxy
ProxyFactory factory = new ProxyFactory();
//設置我們的通知
factory.addAdvice(new MessageDecorator());
//設置我們的目標
factory.setTarget(messageWriter);
//獲取被織入後的代理對象
MessageWriter proxy = (MessageWriter) factory.getProxy();
proxy.wirteMessage();
}
}
用ProxyFactory類來創建一個目標對象的代理,通過織入通知。
通過調用addAdvice()方法把MessageDecorator通知傳給ProxyFactory,然後通過調用setTarget()方法設置織入的目標對象。
就可以調用getProxy()方法獲得一個代理。
(final類不能被擴展--以這種方式)
藉助Spring的ProxyFactory是不是比我們直接用JDK簡單些?(Spring給予了封裝)
Spring AOP架構:
Spring AOP架構的核心是建立在代理上的。
當我們建立被通知類的實例時,我們必須使用ProxyFactory類加入我們需要織入該類的所有通知。
使用ProxyFactory創建AOP帶來是一個完全編程式的做法。
(大部分情況下,我們不需要在應用中直接這麼做,而可以依賴ProxyFactoryBean類來聲明式的創建代理。)
Spring內部有兩種實現代理的方法:JDK動態代理和CGLIB代理。具體可查看DefaultAopProxyFactory的createAopProxy方法。
ProxyFactory類:
ProxyFactory類控制着Spring AOP中的織入和創建的過程。在真正創建代理之前,我們必須指定被通知對象或者說目標對象。
ProxyFactory內部將生成代理的過程交給DefaultAopProxyFactory,後者又根據設置將其交給CglibProxyFactory或者JdkDynamicAopProxy。
有時候我們希望在目標類中的所有方法被調用時都執行通知,而不是一部分方法被調用,可使用addAdvice,但是當我們想對創建的Advisor有更多的控制,或者要向代理中添加一個引入,可以自行創建Advisor,使用addAdvisor。
還提供了removeAdvisor方法。
本章第二部分:在Spring中創建通知
1、通知的接口
前置通知:org.springframework.aop.MethodBeforeAdvice
使用前置通知可以在連接點執行前進行自定義的操作。
不過,Spring中只有一種連接點,即方法調用,所以前置通知就是在你能在方法調用前進行一些操作。
前置通知可以訪問調用的目標方法,也可以對該方法的參數進行操作,不過他不能影響方法調用本身。
後置通知:org.springframework.aop.AfterReturningAdvice
後置通知在方法處的鏈接調用已經完成,並已經返回值時運行。
後置通知可以訪問調用的目標方法,以及該方法的參數和返回值。
因爲等到通知執行時該方法已經調用,後置通知完全不能影響方法調用本身。
環繞通知:org.aopalliance.intercept.MethodInterceptor
Spring中的包圍通知根據AOP聯盟的方法攔截器標準建模。
包圍通知可以在目標方法之前和之後運行,我們也可以定義在什麼時候運行目標方法。
如果需要,我們也可以完全不調用目標方法而寫自己的邏輯。
異常通知:org.springframework.aop.ThrowsAdvice
拋出通知僅在方法調用拋出異常時才被調用,他在目標方法調用返回時執行。
異常通知可以只捕獲特定的異常。
前面的通知都繼承自Advice,可以通過ProxyFactory的addAdvice方法進行加入。
引入:org.springframework.aop.IntroductionInterceptor
Spring將引入看做一個特殊的攔截器。
使用引入攔截器,我們可以定義通知引入的方法的實現。
2、創建前置通知
和前面的demo類似,實現一個簡單的前置打印。
public class SimpleBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("Before method:" + method.getName());
}
public static void main(String[] args) {
MessageWriter messageWriter = new MessageWriter();
ProxyFactory factory = new ProxyFactory();
factory.addAdvice(new SimpleBeforeAdvice());
factory.setTarget(messageWriter);
MessageWriter proxyMessageWriter = (MessageWriter) factory.getProxy();
proxyMessageWriter.wirteMessage();
}
}
3、創建後置通知
顧名思義,後置通知是在方法調用後執行的。既然該方法已經調用了,就沒辦法修改他的參數了。
我們只能讀取,而不能修改目標方法的執行路徑或阻止目標方法執行。
這些約束都在預料之中的,真正出人意料的是我們也不能在後置通知中修改目標方法的返回值。
我們只能夠進行一些新的操作。
雖然我們不能修改返回值,但是我們還是可以跑出一個異常,這樣調用方法就只能看到這個異常而不是返回值了。
(Demo省略了,和前一個沒什麼大的區別)
4、創建包圍通知
包圍通知在功能上綜合了前置通知和後置通知,除了一個重要區別:我們可以修改方法的返回值。
不僅如此,我們還可以阻止目標方法的實際執行。
這意味着用包圍通知,我們可以將目標方法的實現完全更新成新的代碼。
(Demo省略了,前面用過了)
5、創建拋出通知
拋出通知和後置通知一樣是在連接點之後運行的,不過拋出異常只在方法拋出一個異常時才執行。
另外拋出通知對程序本身也不能做任何改變,這和後置通知一樣。
使用拋出通知時我們不能對已經拋出的異常視而不見而爲目標方法返回一個值,我們能做的值是改變拋出異常的類型。
我們用ThrowsAdvice接口來實現拋出通知。
與之前的其他接口不同,ThrowsAdvice接口沒有定義任何方法,他只是Spring使用的一個標誌接口,其原因是Spring允許類型拋出通知,也就是可以定義通知具體接受哪幾類的異常。
Spring通知反射機制尋找固定的方法簽名來實現類型拋出通知。Spring尋找兩種不同的方法簽名。
1.首先方法名稱必須是afterThrowing;
2.可以接收四個參數的形態afterThrowing(Method method, Object[] args, Object target,Exception ex),也可以接受單個參數的形態
3.只匹配“最親近異常捕獲”
public class ErrorBean {
public void errorAll() throws Exception{
throw new Exception("errorAll");
}
public void illegalArgument(){
throw new IllegalArgumentException("illegalArgument");
}
}
public class SimpleThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target,
IllegalArgumentException ex) {
System.out.println("IllegalArgumentException:" + method.getName());
}
public void afterThrowing(Method method, Object[] args, Object target,
Exception ex) {
System.out.println(method.getName());
}
public static void main(String[] args) throws Exception {
ProxyFactory factory = new ProxyFactory();
factory.addAdvice(new SimpleThrowsAdvice());
ErrorBean errorBean = new ErrorBean();
factory.setTarget(errorBean);
ErrorBean proxyErrorBean = (ErrorBean) factory.getProxy();
// proxyErrorBean.errorAll();
proxyErrorBean.illegalArgument();
}
}
7、選擇通知類型
使用最精確的類型可以是代碼的意圖更清晰,同事也能減少出差的可能性。
本章第三部分:Spring裏的通知者和切入點
到目前爲止,全部的示例都用ProxyFactory.addAdvice()方法爲代理設定通知。
該方法會在後臺委派給addAdvisor()方法(注意哦,這裏是Advisor不是Advice),而後者會創建一個DefaultPointcutAdvisor實例並將切入點設爲對所有方法的調用。
這樣,目標的所有方法就都能被通知到了。
在某些情況下,比如用AOP做日誌時,這樣做可能正是我們所需要的,可能在別的情況下,我們希望所通知的是相關的方法而不是所有的方法(那麼就要藉助於切入點 -- Pointcut)。
當然,我們可以在通知內檢查被通知的方法是不是正確的方法,不過這樣做有幾個缺點。
1.將接受的方法名稱列表直接寫進代碼中會降低通知的通用性。使用切入點可以控制哪些方法被通知而無需將列表寫入通知中,顯然這樣做可重用性比較好。
2.爲了確定被調用的方法是否應該被通知,每次目標上的任何一個方法被調用時都需要做一次檢查,這顯然會影響程序的性能。當使用切入點時,每個方法會被檢查一遍,其結果會被緩衝起來供日後使用。
3.Spring使用代理時會優化沒有使用通知的方法,這樣沒有被通知的方法執行起來會被較快。
=====================================================================
講述我們都需要藉助那些類完成
1、使用切入點接口
(切入點接口講解)
在Spring中使用切入點就要使用Pointcut接口。public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
Pointcut定義了兩個方法getClassFilter、getMethodMatcher,分別返回ClassFilter、MethodMatcher。
ClassFilter:
Spring需要確定一個Pointcut是否適用於某個方法,他先會用Pointcut.getClassFilter()返回的ClassFilter測試該方法的類。
public interface ClassFilter {
boolean matches(Class<?> clazz);
}
ClassFilter接口只有一個matches方法,其參數爲一個代表被檢測類的Class實例。如果切入點適用於該類,那麼matches返回true,否則返回false。
MethodMatcher:
相比之下MethodMatcher接口比較複雜。
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object[] args);
}
Spring支持兩種不同的MethodMatcher:靜態的和動態的。一個MethodMatcher具體是哪一種可以由isRuntime返回值確定,如果返回false,那麼該MethodMatcher是靜態的,反之爲動態的。
如果切入點是靜態的,那麼Spring會針對目標的每一個方法調用一次MethodMatcher的matches(Method method, Class<?> targetClass)方法,其返回值被緩存起來便以後調用該方法時使用。這樣,對每一個方法的實用性測試只會進行一次,之後調用該方法時不會再調用mathches方法了。
如果切入點是動態的,Spring仍然會在目標方法第一次調用時用matches(Method method, Class<?> targetClass)進行一個靜態的測試來檢查起總的適用性。不過如果該測試返回true,那麼在此基礎上,每次該方法被調用時Spring還會再次調用matches(Method method, Class<?> targetClass, Object[] args)方法。
大多數MethodMatcher是靜態的,這意味着isRuntime()方法返回false。在這種情況下,matches(Method, Class , Object[])永遠不會被調用。
通常很少有人會自己編寫Pointcut的實現,因爲Spring提供了動態切入點和靜態切入點的抽象基類。
接下來,我們學習這些基類以及他們的Pointcut實現:
1.已有的切入點實現概覽
Spring提供了Pointcut接口的10個實現,其中兩個抽象類分別用來簡化創建靜態和動態切入點,另外8個是獨立類。
ComposablePointcut:可以通過union()、intersection()等操作組合兩個或兩個以上的切入點。
ControlFlowPointcut:是一種特殊的切入點,他匹配另一個方法的流程中包含的所有方法,也就是另一個方法執行時直接或間接調用的所有方法。
JdkRegexpMethodPointcut:可以用JDK1.4支持的正則表達式定義切入點。
NameMatchMethodPointcut:我們創建一個切入點來簡單的匹配一個方法名列表。
StaticMethodMatcherPointcut:用來作爲構建靜態切入點的基類。
DynamicMethodMatcherPointcut:一個可以方便的構建用來在運行時瞭解方法參數的動態切入點的基類。
AnnotationMatchingPointcut:用來創建Java 5註解切入點。
AspectJExpressionPointcut:用來通過AspectJ表達式語言定義切入點。(目前只能定義方法執行切入點)
2.DefaultPointcutAdvisor
在使用任何Pointcut之前,必須先生成一個Advisor,更準確的說是一個PointcutAdvisor。
Spring來使用Advisor來表示方面,即通知和切入點的結合,其中切入點定義那些地方應該被通知以及如何通知。
DefaultPointcutAdvisor用來結合一個Pointcut和一個Advice的簡單切入點通知。
====================================================================================
創建一個Pointcut
(會對“已有的切入點實現概覽”提到的切入點挨個實現)
1.用StaticMethodMatcherPointcut創建靜態切入點
StaticMethodMatcherPointcut只需要我們實現一個方法--boolean matches(Method method, Class<?> targetClass),而Pointcut的其他實現是自動完成的。但是有時候我們也需要覆蓋getClassFilter()來保證只有正確類型的方法纔會被通知。
Demo(來結合前面提到的DefaultPointcutAdvisor和StaticMethodMatcherPointcut):
我們定義了兩個完全相同的類,但是我們只通知其中BeanOne的foo()方法。
public class BeanOne {
public void foo(){
System.out.println("foo");
}
public void bar(){
System.out.println("bar");
}
}
public class BeanTwo {
public void foo(){
System.out.println("foo");
}
public void bar(){
System.out.println("bar");
}
}
創建Pointcut:
public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return ("foo".equals(method.getName()));
}
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return (clazz == BeanOne.class);
}
};
}
}
創建Advice:
public class SimpleAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("invoke:" + invocation.getMethod().getName());
return invocation.proceed();
}
}
然後創建一個Advisor,把前面的Pointcut和Advice組合起來:
public class StaticPointcutExample {
public static void main(String[] args) {
BeanOne beanOne = new BeanOne();
BeanTwo beanTwo = new BeanTwo();
Pointcut pointcut = new SimpleStaticPointcut();
Advice advice = new SimpleAdvice();
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
ProxyFactory factory1 = new ProxyFactory();
factory1.addAdvisor(advisor);
factory1.setTarget(beanOne);
BeanOne proxyBeanOne = (BeanOne) factory1.getProxy();
ProxyFactory factory2 = new ProxyFactory();
factory2.addAdvisor(advisor);
factory2.setTarget(beanTwo);
BeanTwo proxyBeanTwo = (BeanTwo) factory2.getProxy();
proxyBeanOne.foo();
proxyBeanTwo.foo();
proxyBeanOne.bar();
proxyBeanTwo.bar();
}
}
2.使用DynamicMethodMatcherPointcut創建動態切入點
Demo2:
public class BeanOne { public void foo(int x){ System.out.println("foo:" + x); } public void bar(){ System.out.println("bar"); } } public class BeanTwo { public void foo(int x){ System.out.println("foo:" + x); } public void bar(){ System.out.println("bar"); } }
和前面demo不同的是隻有BeanOne的foo參數爲100時才發起通知。
和靜態切入點一樣,也提供了一個DynamicMethodMatcherPointcut基類,只有一個抽象方法boolean matches(Method method, Class<?> targetClass, Object[] args)。不過我們會看到,明智的做法是同時也要實現boolean matches(Method method, Class<?> targetClass)方法以控制靜態檢測。
public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {
//動態切入也加入此方法的好處是,在調用bar方法時,只是第一次做靜態判斷,以後也不會判斷。如果不加,每次調用bar都會做動態切入點判斷。
@Override
public boolean matches(Method method, Class<?> targetClass) {
System.out.println(targetClass.getSimpleName() + " Static check for " + method.getName());
return ("foo".equals(method.getName()));
}
//動態切入點判斷
@Override
public boolean matches(Method method, Class<?> targetClass, Object[] args) {
System.out.println(targetClass.getSimpleName() + " Dynamic check for " + method.getName());
return (args != null && args.length > 0 && (Integer) args[0] == 100);
}
//過濾了類,只對BeanOne做動態或靜態matches判斷,不對BeanTwo做matches(方法)判斷。
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return (clazz == BeanOne.class);
}
};
}
}
通常創建一個切入點時,我們想要基於方法名稱來匹配,而忽略方法簽名和返回類型。
在這種情況下,我們可以用NameMatchMethodPointcut來匹配一組方法名稱,而不需要創建StaticMethodMatcherPointcut。
使用NameMatchMethodPointcut時,不用理會方法的簽名,所以名稱foo可以匹配foo(),也可以匹配poo(int)。
Demo:
public class NameBean {
public void foo(){
System.out.println("foo");
}
public void foo(int x){
System.out.println("foo " + x);
}
public void bar(){
System.out.println("bar");
}
public void yup(){
System.out.println("yup");
}
}
public class NamePointcutExample {
public static void main(String[] args) {
NameBean nameBean = new NameBean();
NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();
nameMatchMethodPointcut.addMethodName("foo");
nameMatchMethodPointcut.addMethodName("bar");
Advisor advisor = new DefaultPointcutAdvisor(nameMatchMethodPointcut, new SimpleAdvice());
ProxyFactory factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(nameBean);
NameBean bean = (NameBean) factory.getProxy();
bean.foo();
bean.foo(23);
bean.bar();
bean.yup();
}
}
我們不需要爲切入點創建一個類,只需要簡單地創建一個NameMatchMethodPointcut實例就可以了。
我們用addMethodName()方法向切入點加入了兩個名字foo和bar。
4.用正則表達式創建切入點
正則表達式具有強大的匹配功能,所以能把正則用在pointcut匹配中,將會“功力大增”。
public class RegexpPointcutExample {
public static void main(String[] args) {
RegexpBean bean = new RegexpBean();
JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();
jdkRegexpMethodPointcut.setPattern(".*foo.*");
Advisor advisor = new DefaultPointcutAdvisor(jdkRegexpMethodPointcut, new SimpleAdvice());
ProxyFactory factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(bean);
RegexpBean proxyBean = (RegexpBean) factory.getProxy();
proxyBean.foo1();
}
}
5.通知者的便利實現
對於很多Pointcut實現,Spring也提供了想對的Advisor的便利實現。這些實現也可以當做Pointcut使用。
比如,前面的NameMatchMethodPointcut和DefaultPointcutAdvisor,我們可以直接用NameMatchMethodPointcutAdvisor提交,這樣更加簡潔。
Demo:
public class NamePointcutUsingAdvisor {
public static void main(String[] args) {
NameBean target = new NameBean();
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(new SimpleAdvice());
advisor.addMethodName("foo");
ProxyFactory factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(target);
NameBean bean = (NameBean) factory.getProxy();
bean.foo();
}
}
6.使用AspectJExpressionPointcut
AspectJExpressionPointcut類讓你能夠編寫AspectJ表達式來定義一個切入點。
Demo:(這裏我們只是做簡單展示,後面會具體簡單介紹AspectJ語言)
public class AspectJExpressionPointcutDemo {
public static void main(String[] args) {
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setAdvice(new SimpleAdvice());
advisor.setExpression("execution(* com.partner4java..*.foo*(..))");
ProxyFactory factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(new RegexpBean());
RegexpBean bean = (RegexpBean) factory.getProxy();
bean.foo1();
System.out.println("---");
bean.bar();
}
}
7.使用AnnotationMatchingPointcut
考慮這樣一種情況:在測試時處於某種目的,我們想對若干方法進行通知。但是這些方法都來自不同的包和類中。
另外我們希望儘可能少的配置來對要監控的方法或類進行修改。
一種解決方式就是註解,在所有希望通知的類或方法上使用該註解。
這邊是AnnotationMatchingPointcut的用武之地。我們可以使用這個便捷的類爲指定了標註的方法或類定義一個切入點。
Demo:
首先自定義一個註解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface SimpleAnnotation {
}
給要通知的方法加上註解:
public class RegexpBean {
@SimpleAnnotation
public void foo1(){
System.out.println("foo1");
}
public void foo2(){
System.out.println("foo2");
}
然後藉助AnnotationMatchingPointcut:
public class AnnotationMatchingPointcutDemo {
public static void main(String[] args) {
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(null, SimpleAnnotation.class);
Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());
ProxyFactory factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(new RegexpBean());
RegexpBean bean = (RegexpBean) factory.getProxy();
bean.foo1();
}
}
AnnotationMatchingPointcut接收了兩個參數,一個類級別的註解類和一個方法級別的註解類。只傳入了類級別註解:那麼當某類在類上添加本註解,添加註解類的所有方法都會被通知。
只傳入方法級別註解:那麼當某方法添加註解,僅本方法會被通知。
兩個參數都傳入:那麼想要使某方法被通知,必須在本方法和方法的類上都添加註解。
8.使用控制流切入點
Spring的控制流切入點是由ControlFlowPointcut類實現的。
簡單說,Spring控制流切入點匹配一個類中對某一個方法或所有方法的調用。
但是,如果你對性能有所要求,儘量避免使用。
public class ControlFlowDemo {
public static void main(String[] args) {
Pointcut pointcut = new ControlFlowPointcut(ControlFlowDemo.class, "test");
Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());
ProxyFactory factory = new ProxyFactory();
RegexpBean target = new RegexpBean();
factory.setTarget(target);
factory.addAdvisor(advisor);
RegexpBean proxy = (RegexpBean) factory.getProxy();
hello(proxy);
System.out.println("========");
test(proxy);
System.out.println("========");
test();
}
public static void hello(RegexpBean proxy){
proxy.foo1();
}
public static void test(RegexpBean proxy){
proxy.foo2();
}
public static void test(){
RegexpBean proxy = new RegexpBean();
proxy.foo2();
}
}
打印:
foo1
========
invoke:foo2
foo2
========
foo2
9、使用ComposablePointcut
在前面的切入示例中,我們每個Advisor中只使用了一個切入點(當然,你可以同時添加多個,因爲顯而易見,ProxyFactory它提供的是addAdvisor,而不是setAdvisor)。
但是有時我們需要組合兩個或更多的切入點才能滿足需求。用ComposablePointcut可以對多個切入點進行組合,組合成一個。
ComposablePointcut支持兩個方法:union()和interserction()。默認情況下,ComposablePointcut在創建時附帶的ClassFilter會匹配所有的類,而其MethodMatcher匹配所有方法,不過我們可以在構造器中提供自己的ClassFilter和MethodMatcher。
union()和intersection()方法都有重載的版本可接受ClassFilter和MethodMatcher參數。
調用接受MethodMatcher的union()方法會將ComposablePointcut的MethodMatcher改爲一個UnionMethodMatcher,後者是ComposablePointcut現有的MethodMatcher的並集。
UnionMethodMatcher所包含的兩個MethodMatcher中任何一個返回true,UnionMethodMatcher就會返回true。
union()方法可以調用任意多次,每次調用都會生成一個新的UnionMethodMatcher,來包含ComposablePointcut中當前的MethodMatcher和傳給union()的MethodMatcher。
將ClassFilter傳給union()也會生成類似的結構。
在其內部,intersection()方法的工作原理和union()非常類似。不過,IntersectionMethodMatcher類只在其包含的兩個MethodMatcher都返回true時纔會返回true。
Demo:
public class ComposablePointcutDemo {
public static void main(String[] args) {
ComposablePointcut pointcut = new ComposablePointcut();
Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());
ProxyFactory factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(new RegexpBean());
RegexpBean bean = (RegexpBean) factory.getProxy();
bean.foo1();
pointcut.intersection(new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().equals("foo2");
}
});
System.out.println("-------------------");
factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(new RegexpBean());
bean = (RegexpBean) factory.getProxy();
bean.foo1();
}
}
可能你會有疑問,我們上面學到的種種基於Spring的AOP實現,全部都是編碼的方式,那麼是否可以通過XML配置文件的方式實現呢?
我們把AspectJExpressionPointcutDemo轉換成XML形式:
只需要把main裏面的步驟轉換爲XML:
<!-- 通知 -->
<bean id="simpleAdvice" class="com.partner4java.advisor.demo1.SimpleAdvice"></bean>
<!-- 幫助創建pointcut然後和advice結合成advisor -->
<bean id="mAspectJExpressionPointcutAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="simpleAdvice"/>
<property name="expression" value="execution(* com.partner4java..*.foo*(..))"/>
</bean>
<!-- 被代理的對象 -->
<bean id="regexpBean" class="com.partner4java.advisor.demo4.RegexpBean"/>
<bean id="proxyRegexpBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="regexpBean"/>
<property name="interceptorNames">
<set>
<value>mAspectJExpressionPointcutAdvisor</value>
</set>
</property>
</bean>
測試:
public class AspectJExpressionPointcutDemo {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/hello_aop.xml");
RegexpBean bean = (RegexpBean) applicationContext.getBean("proxyRegexpBean");
bean.foo1();
System.out.println("---");
bean.bar();
}
}
AnnotationMatchingPointcutDemo這種註解形式的如何轉換呢?
如果你認真學習了上一章(《partner4java 講述Spring入門》之第一步:Spring概述與Spring IoC http://blog.csdn.net/partner4java/article/details/8194747)應該不難推測到。
現在你又有一個疑問,我們不是已經把Advisor交容器了麼?能不能省略ProxyFactoryBean,自動代理所有bean?
你只需要在配置文件裏面加:<aop:aspectj-autoproxy />
測試:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/hello_aop.xml");
RegexpBean bean = (RegexpBean) applicationContext.getBean("regexpBean");
bean.foo1();
System.out.println("---");
bean.bar();
第三部分:Spring中AOP使用升級篇
Spring AOP的實現分爲兩個邏輯組成部分。
1、是AOP核心,他提供了完全解耦的、純編程的式的AOP功能。
2、是讓我們能在程序更簡便的使用AOP而給出的一組框架服務。
第一條我們前面已經學過了,編碼方式,最後也給你略帶了如何轉爲XML形式。接下來我們學習第二條:在程序更簡便的使用AOP
如果你足夠“苛刻”,你又會對框架提出問題:
1、可不可以藉助註解使代碼更簡潔?
2、是否可以基本不寫代碼(指我們的Advisor和Pointcut),通過配置文件來實現?
===========================================================================================
解決第一個問題(藉助@AspectJ):
1、@AspectJ註解 -- helloworld
三步:
@AspectJ和AspectJ沒有關係。它是Spring用來解析連接點和通知的一組Java 5註解。
首先看一個簡單的日誌Demo:
第一步:定義我們的aspect
@Aspect
public class LoggingAspect {
@Around("execution(* com.partner4java.*.*.*(..))")
public Object log(ProceedingJoinPoint point) throws Throwable{
System.out.println("Before" + point.getTarget().getClass());
return point.proceed();
}
}
第二步:打開我們的註解,並把定義的aspect交給spring
<!-- 掃描我們指定目錄的註解類解析爲Bean -->
<context:component-scan base-package="com.partner4java"/>
<!-- 寫進XML,會自動被容器識別 -->
<bean class="com.partner4java.annotation.LoggingAspect"/>
<!-- 開啓自動識別 -->
<aop:aspectj-autoproxy/>
第三步:測試
@Component
public class TestBean {
public void work(){
System.out.println("work");
}
}
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"/META-INF/spring/aspectj-demo.xml");
TestBean testBean = applicationContext.getBean("testBean",
TestBean.class);
testBean.work();
}
<aop:aspectj-autoproxy/>Enables the use of the @AspectJ style of Spring AOP.
2、@AspectJ方面詳解
1.切入點
在上一個Demo中我們切入點寫在了包圍通知中,當然,我們切入點也可以單獨拿出來:
@Pointcut("execution(* com.partner4java.annotation.*.*.*(..))")
private void testBeanExecution(){
}
然後我們可以在本類通知中通過方法名引用這個切入點:
@Around("testBeanExecution()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("Before" + joinPoint.getTarget().getClass());
return joinPoint.proceed();
}
如果你想在其他@Aspect聲明類中使用本切入點,可以把切入點方法private權限級別加以修改。
在其他類中引用加上類名,如:
@Around("LoggingAspect.testBeanExecution()")
2.切入點表達式
execution:匹配方法執行連接點。我們可以指定包、類或者方法名,以及方法的可見性、返回值和參數類型。這是應用的最爲廣泛的切入點表達式。
within:匹配那些在已聲明的類型中執行的連接點。
this:通過用bean引用的類型跟指定的類型做對比來匹配連接點。
args:通過比較方法的參數類型跟指定的參數類型來匹配連接點。
@target:通過檢查調用目標對象是否具有特定註解來匹配連接點。
@args:跟args類似,不過@args檢查的是方法參數的註解而不是他們的類型。
@within:跟within相似,這個表達式匹配那些帶有特定註解的類中執行的連接點。
@annotation:通過檢查講被調用的方法上的註解是否爲指定的註解來匹配連接點。
bean:通過比較bean的ID來匹配連接點,我們也可以在bean名模式中使用通配符。
我們可以使用||(或)和&&(與)運算符來組合多個切入點表達式,並使用!(非)運算符來對錶達式值取否。
如:execution(* com.partner4java..*.*(..)) && within(com.partner4java.annotation.demo1.TestBean)
也可以把帶有@Pointcut註解的方法跟其他帶有@Pointcut註解的方法或一個切入點表達式組合起來:
@Pointcut("testPointcut1() && testPointcut2()")
3.探討切入點表達式
execution表達式:
Execution表達式語法在Spring中的用法跟AspectJ表達式的語法相同。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
後綴(?)表示可選的表達式元素。
如:
* com.partner4java.demo.Test.*(..)
第一個*表示任何返回類型;後面跟着一個全限定名;這個類名後又跟領一個星號*(..),這表示一個任意名稱、任意數量和任意參數類型。
因爲我們沒有指定修飾符模式(modifiers-pattern)或異常拋出模式(throws-pattern),Spring AOP將匹配任意修飾符和拋出任意異常的方法。
爲了匹配com.partner4java子包中的任何類任何方法,我們可以將表達式寫爲* com.partner4java.demo..*.*(..)
@Aspect
public class LoggingAspect {
@Around("execution(* com.partner4java..*.*(..)) && !execution(* com.partner4java..*.get*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("Before" + joinPoint.getTarget().getClass());
return joinPoint.proceed();
}
}
within表達式:
within(declaring-type-pattern)
例如:
要聲明一個可以匹配在com包及其子包的任意類中對任意方法調用的執行過程的切入點,withi(com..*)
this表達式:
this切入點的語義會匹配某一對象上所有的方法執行過程,不能使用..或*這樣的通配符。
target表達式:
target(class-name)
target表達式和this完全一樣。
args表達式:
args(type-pattern? (,type-pattern)*) 我們可以指定零個、一個或多個type-pattern表達式。
4.通知的類型
問題:
從第5版與AspectWerkz合併起,AspectJ支持將其aspect編寫爲帶有一租AspectJ註解的POJO。
Spring AOP框架也支持這種aspect,但是這些aspect必須在Spring IoC容器中註冊方可生效。
解決方案:
要在Spring中註冊AspectJ aspect,只需要將他們聲明爲IoC容器中的Bean實例就行了。
在Spring IoC容器中啓用AspectJ,容器將自動爲匹配AspectJ aspect的Bean創建代理。
用AspectJ註解編寫的aspect只是一個帶有@Aspect註解的java類。通知(Advice)是帶有一個通知註解的簡單Java方法。
AspectJ註解5中通知註解:@Before、@After、@AfterReturning、@AfterThrowing和@Around。
工作原理:
前置通知:
@Before
方式一:簡單方式,只是添加註解
@Aspect
public class LoggingBeforeAspect {
private Log log = LogFactory.getLog(LoggingBeforeAspect.class);
@Before("execution(* *..*.delete(..))")
public void logBefore(){
log.debug("The method add() begins");
}
}
然後把這個class註冊進IoC就可以,可以以匿名的方式註冊
<aop:aspectj-autoproxy />
<bean id="userDao" class="com.partner4java.aspectj.demo1.UserDaoImpl"/>
<bean class="com.partner4java.aspectj.demo1.LoggingBeforeAspect"/>
方式二:我們還可以拿到連接點
@Aspect
public class LoggingBeforeAspect1 {
private Log log = LogFactory.getLog(LoggingBeforeAspect1.class);
@Before("execution(* *..*.delete(..))")
public void logBefore(JoinPoint joinPoint){
log.debug("The method "+ joinPoint.getSignature().getName() +" add() begins");
}
}
最終通知:
最終通知(after advice)在連接點結束之後執行,不管返回結果還是拋出異常。
@Aspect
public class LoggingAfterAspect {
private Log log = LogFactory.getLog(LoggingAfterAspect.class);
@After("execution(* *..*.delete(..))")
public void logBefore(JoinPoint joinPoint) {
log.debug("The method " + joinPoint.getSignature().getName() + " ends");
}
}
後置通知:
最終通知不管連接點正常返回還是拋出異常都執行。
如果你希望僅當連接點返回時記錄,應該用後置通知(after returning advice)替換最終通知。
在後置通知中,你可以在@AfterReturning註解中添加一個returning屬性,訪問連接點的返回值。這個參數值應該是通知方法的參數名稱,用於傳入返回值。
@Aspect
public class LoggingAfterReturnAspect {
private Log log = LogFactory.getLog(LoggingAfterReturnAspect.class);
@AfterReturning(pointcut = "execution(* *..*.delete(..))", returning = "result")
public void logBefore(JoinPoint joinPoint, Object result) {
log.debug("The method " + joinPoint.getSignature().getName()
+ " ends with" + result);
}
}
異常通知:
僅當連接點拋出異常時執行。
@Aspect
public class LoggingErrorAspect {
private Log log = LogFactory.getLog(LoggingErrorAspect.class);
@AfterThrowing(pointcut = "execution(* *..*.save(..))", throwing = "throwable")
public void logAfterThrowing(JoinPoint joinPoint, Throwable throwable) {
log.debug("exception " + throwable + " in method"
+ joinPoint.getSignature().getName());
}
}
環繞通知:
功能最強大的,可以滿足前面的所有需求,而且可以改變返回的數值。
@Aspect
public class LoggingAroundAspect {
private Log log = LogFactory.getLog(LoggingErrorAspect.class);
@Around("execution(* *..*.find(..))")
public Object logAround(ProceedingJoinPoint joinPoint){
log.debug("being " + joinPoint.getSignature().getName());
try {
Object result = joinPoint.proceed();
log.debug(" end " + result);
return result;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
選擇通知類型的通用原則是使用滿足你的要求的最不強大的類型。
問題:
在編寫aspect時,你可以直接在通知註解中嵌入切入點表達式。但是,相同的切入點表達式在多個通知中必須重複。
解決方案:
和許多其他AOP實現一樣,AspectJ也允許獨立的定義切入點,在多個通知中重用。
工作原理:
如果你是隻在本類中共享,可定義一個private的方法,然後放上切入點,在其他的方法上,直接引用其方法名就可以。
@Aspect
public class LoggingAspectj {
@Pointcut("execution(* *..*.find(..))")
private void loggingOpertion(){}
@Before("loggingOpertion()")
public void logBefore(){
}
}
當然,你也可以把loggingOpertion設置爲public的,但是在同包下需要加上類名@Before("LoggingAspectj.loggingOpertion()"),
如果跨包加上包名。
問題:
有時候,你可能希望爲一組現有的對象添加新的狀態,跟蹤他們的使用情況,如調用次數、最後修改日期等。
如果所有的對象都有相同的基類,這就不成問題。
但是,如果不同的類不在相同的類層次結構中,添加這樣的狀態就很難。
解決方案:
你可以爲你的對象引入一個新的接口和保存狀態字段的實現類。然後,你可以編寫另一個通知根絕特定的條件改變狀態。
工作原理:
定義一個接入的接口和實現類,然後通過AspectJ引入到目標類中。
<aop:aspectj-autoproxy />
<bean id="userDao" class="com.partner4java.aspectj.demo1.UserDaoImpl"/>
<bean class="com.partner4java.aspectj.declare.DeclareLogAspectj"/>
@Aspect
public class DeclareLogAspectj {
//value屬性標識引入的目標類
//引入的接口,爲被註解的定義類,實現爲defaultImpl指定的類
@DeclareParents(value = "com.partner4java.aspectj.demo1.*", defaultImpl = ExecuteLogImpl.class)
public ExecuteLog executeLog;
//
@Around("execution(* *.*(..))" + " && this(executeLog)")
public Object declareLog(ProceedingJoinPoint joinPoint,
ExecuteLog executeLog) {
executeLog.setBeginDate(new Date());
try {
Object result = joinPoint.proceed();
executeLog.increase();
executeLog.setEndDate(new Date());
return result;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
獲取的時候可以直接讓被接入的類強轉爲接入類的接口類型:
@Test
public void testAsp(){
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.find(new Integer(22));
ExecuteLog executeLog = (ExecuteLog)userDao;
System.out.println(executeLog.getCount());
System.out.println(executeLog.getBeginDate());
}
當使用@AspectJ自動代理時要強制使用CGLIB,請將<aop:aspectj-autoproxy> 的proxy-target-class屬性設置爲true:
<aop:aspectj-autoproxy proxy-target-class="true"/>
解決第二個問題:
問題:
如果你不想以任何理由使用註解的形式。
解決方案:
基於Spring的XML配置方式。
工作原理:
可以去掉<aop:aspectj-autoproxy />開啓註解的聲明。
<aop:config>
<aop:pointcut id="loggingOperation" expression=
"within(com.apress.springrecipes.calculator.ArithmeticCalculator+) || within(com.apress.springrecipes.calculator.UnitCalculator+)" />
<aop:pointcut id="validationOperation" expression=
"within(com.apress.springrecipes.calculator.ArithmeticCalculator+) || within(com.apress.springrecipes.calculator.UnitCalculator+)" />
<aop:aspect id="loggingAspect" ref="calculatorLoggingAspect">
<aop:before pointcut-ref="loggingOperation"
method="logBefore" />
<aop:after-returning pointcut-ref="loggingOperation"
returning="result" method="logAfterReturning" />
<aop:after-throwing pointcut-ref="loggingOperation"
throwing="e" method="logAfterThrowing" />
<aop:around pointcut-ref="loggingOperation"
method="logAround" />
</aop:aspect>
<aop:aspect id="validationAspect" ref="calculatorValidationAspect">
<aop:before pointcut-ref="validationOperation"
method="validateBefore" />
</aop:aspect>
<aop:aspect id="introduction" ref="calculatorIntroduction">
<aop:declare-parents
types-matching=
"com.apress.springrecipes.calculator.ArithmeticCalculatorImpl"
implement-interface=
"com.apress.springrecipes.calculator.MaxCalculator"
default-impl=
"com.apress.springrecipes.calculator.MaxCalculatorImpl" />
<aop:declare-parents
types-matching=
"com.apress.springrecipes.calculator.ArithmeticCalculatorImpl"
implement-interface=
"com.apress.springrecipes.calculator.MinCalculator"
default-impl=
"com.apress.springrecipes.calculator.MinCalculatorImpl" />
<aop:declare-parents
types-matching=
"com.apress.springrecipes.calculator.*CalculatorImpl"
implement-interface=
"com.apress.springrecipes.calculator.Counter"
default-impl=
"com.apress.springrecipes.calculator.CounterImpl" />
<aop:after pointcut=
"execution(* com.apress.springrecipes.calculator.*Calculator.*(..)) and this(counter)"
method="increaseCount" />
</aop:aspect>
</aop:config>
這個就不多說了,自己動手,仔細看看就明白了。
《partner4java 講述Spring入門》之第一步:Spring概述與Spring IoC
http://blog.csdn.net/partner4java/article/details/8194747
《partner4java 講述Spring入門》之第二步:Spring AOP
http://blog.csdn.net/partner4java/article/details/8239721