動態代理實現日誌的寫入
之前在學習設計模式的時候就學習過代理模式,在DRP的學習過程中,又一次遇到了代理模式,但是這次接觸到的是動態代理。做專案的時候也聽同學們提到過AOP,那麼動態代理和AOP是一種什麼樣的關係呢?
一、代理定義
圖1 代理模式類圖
代理模式:給某一個物件提供一個代理,並由代理物件控制對原物件的引用。
代理模式能夠協調呼叫者和被呼叫這,在一定的程度上降低了系統的耦合度,但是由於在客戶端和真實主題之間增加了代理物件,因此有些型別的代理模式可能會造成請求的處理速度變慢。
二、動態代理
動態代理是一種較為高階的代理模式,它的典型應用就是Spring AOP.
在傳統的代理模式中,客戶端通過Proxy呼叫RealSubject類的request()方法,同時還在代理類中封裝了其他方法(如preRequest()和postRequest()),可以處理一些其他問題,如果按照這種方式使用代理模式,那麼真實主題角色必須是事先已經存在的,並將其作為代理物件的內部成員屬性。如果一個真實主題角色必須對應一個代理主題角色,這將導致系統中類的個數急劇增加,因此需要想辦法減少系統中類的個數,在實現不知道真實主題角色的情況下使用代理主體角色,這就是動態代理需要解決的問題。
三、例項程式碼
1.主題介面
public interface UserManager {
public void addUser(String userId, String userName);
public void delUser(String userId);
public void modifyUser(String userId, String userName);
public String findUser(String userId);
}
2.真實主題
public class UserManagerImpl implements UserManager { public void addUser(String userId, String userName) { try { System.out.println("UserManagerImpl.addUser() userId-->>" + userId); }catch(Exception e) { e.printStackTrace(); throw new RuntimeException(); } } public void delUser(String userId) { System.out.println("UserManagerImpl.delUser() userId-->>" + userId); } public String findUser(String userId) { System.out.println("UserManagerImpl.findUser() userId-->>" + userId); return "張三"; } public void modifyUser(String userId, String userName) { System.out.println("UserManagerImpl.modifyUser() userId-->>" + userId); } }
3.動態代理
Java動態代理實現相關類位於java.lang.reflect包,主要涉及兩個物件,InvocationHandler介面和Proxy類。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogHandler implements InvocationHandler {
private Object targetObject;
//Proxy為動態代理類
public Object newProxyInstance(Object targetObject) {
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), this);
}
/*
* @proxy:表示代理類
* @method:表示需要代理的方法
* @args:表示代理方法的引數陣列
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("start-->>" + method.getName());
for (int i=0; i<args.length; i++) {
System.out.println(args[i]);
}
Object ret = null;
try {
//呼叫目標方法
ret = method.invoke(targetObject, args);
System.out.println("success-->>" + method.getName());
}catch(Exception e) {
e.printStackTrace();
System.out.println("error-->>" + method.getName());
throw e;
}
return ret;
}
}
在newProxyInstance方法中,目標主題作為引數,我們通過targetObject.getClass().getClassLoader()獲取ClassLoader物件,然後通過targetObject.getClass().getInterfaces()獲取它實現的所有介面,這樣就在記憶體中建立了一個動態代理物件。但是值得注意的一點是,這個被建立的動態代理類裡面只有目標主題的方法,而沒有其實現,當客戶端呼叫的動態代理類的方法時,需要呼叫一個實現了InvocationHandler介面的類,這樣才能代用InvocationHandler中的invoke()回撥方法,所以要把LogHandler自身作為引數傳入。
4.客戶端呼叫
public class Client {
public static void main(String[] args) {
LogHandler logHandler = new LogHandler();
UserManager userManager = (UserManager)logHandler.newProxyInstance(new UserManagerImpl());
String name = userManager.findUser("0001");
System.out.println("Client.main() --- " + name);
}
}
在這裡,userManager作為代理物件,呼叫userManager.findUser()就會呼叫InvocationHandler中的invoke方法,所以在invoke()方法中處理相應的日誌操作即可。
總結:
代理模式的進一步深入學習,越發感覺到面向物件程式設計的魅力。invoke()可以聯想到在學習js的回撥函式,所以接收起來並不太陌生。動態代理模式解決了靜態代理模式要求建立多個代理類的缺陷,把編譯時建立的例項延遲到執行時。AOP將日誌記錄,效能統計,安全控制,事務處理,異常處理等程式碼從業務邏輯程式碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的程式碼。無疑,代理模式為AOP的實現提供了良好的解決思路。