java靜態代理和動態代理的區別
本篇部落格的由來,之前我們學習大話設計,就瞭解了代理模式,但為什麼還要說呢?
原因:
1,通過DRP這個專案,瞭解到了動態代理,認識到我們之前一直使用的都是靜態代理,那麼動態代理又有什麼好處呢?它們二者的區別是什麼呢?
2,通過學習動態代理了解到動態代理是一種符合AOP設計思想的技術,那麼什麼又是AOP?
下面是我對它們的理解!
代理Proxy:
Proxy代理模式是一種結構型設計模式,主要解決的問題是:在直接訪問物件時帶來的問題
代理是一種常用的設計模式,其目的就是為其他物件提供一個代理以控制對某個物件的訪問。代理類負責為委託類預處理訊息,過濾訊息並轉發訊息,以及進行訊息被委託類執行後的後續處理。
為了保持行為的一致性,代理類和委託類通常會實現相同的介面,所以在訪問者看來兩者沒有絲毫的區別。通過代理類這中間一層,能有效控制對委託類物件的直接訪問,也可以很好地隱藏和保護委託類物件,同時也為實施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。
更通俗的說,代理解決的問題當兩個類需要通訊時,引入第三方代理類,將兩個類的關係解耦,讓我們只瞭解代理類即可,而且代理的出現還可以讓我們完成與另一個類之間的關係的統一管理,但是切記,代理類和委託類要實現相同的介面,因為代理真正呼叫的還是委託類的方法。
使用場合舉例:
如果需要委託類處理某一業務,那麼我們就可以先在代理類中統一處理然後在呼叫具體實現類
按照代理的建立時期,代理類可以分為兩種:
靜態:由程式設計師建立代理類或特定工具自動生成原始碼再對其編譯。在程式執行前代理類的.class檔案就已經存在了。
動態:在程式執行時運用反射機制動態建立而成。
下面分別用靜態代理與動態代理演示一個示例:
新增列印日誌的功能,即每個方法呼叫之前和呼叫之後寫入日誌
靜態代理:
具體使用者管理實現類
- public class UserManagerImpl implements UserManager {
- public void addUser(String userId, String userName) {
- System.out.println("UserManagerImpl.addUser"
- }
- public void delUser(String userId) {
- System.out.println("UserManagerImpl.delUser");
- }
- public String findUser(String userId) {
- System.out.println("UserManagerImpl.findUser");
- return "張三";
- }
- public void modifyUser(String userId, String userName) {
- System.out.println("UserManagerImpl.modifyUser");
- }
- }
代理類--代理使用者管理實現類
- public class UserManagerImplProxy implements UserManager {
- // 目標物件
- private UserManager userManager;
- // 通過構造方法傳入目標物件
- public UserManagerImplProxy(UserManager userManager){
- this.userManager=userManager;
- }
- public void addUser(String userId, String userName) {
- try{
- //新增列印日誌的功能
- //開始新增使用者
- System.out.println("start-->addUser()");
- userManager.addUser(userId, userName);
- //新增使用者成功
- System.out.println("success-->addUser()");
- }catch(Exception e){
- //新增使用者失敗
- System.out.println("error-->addUser()");
- }
- }
- public void delUser(String userId) {
- userManager.delUser(userId);
- }
- public String findUser(String userId) {
- userManager.findUser(userId);
- return "張三";
- }
- public void modifyUser(String userId, String userName) {
- userManager.modifyUser(userId,userName);
- }
- }
客戶端呼叫
- public class Client {
- public static void main(String[] args){
- //UserManager userManager=new UserManagerImpl();
- UserManager userManager=new UserManagerImplProxy(new UserManagerImpl());
- userManager.addUser("1111", "張三");
- }
- }
靜態代理類優缺點
優點:
代理使客戶端不需要知道實現類是什麼,怎麼做的,而客戶端只需知道代理即可(解耦合),對於如上的客戶端程式碼,newUserManagerImpl()可以應用工廠將它隱藏,如上只是舉個例子而已。
缺點:
1)代理類和委託類實現了相同的介面,代理類通過委託類實現了相同的方法。這樣就出現了大量的程式碼重複。如果介面增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度。
2)代理物件只服務於一種型別的物件,如果要服務多型別的物件。勢必要為每一種物件都進行代理,靜態代理在程式規模稍大時就無法勝任了。如上的程式碼是隻為UserManager類的訪問提供了代理,但是如果還要為其他類如Department類提供代理的話,就需要我們再次新增代理Department的代理類。
舉例說明:代理可以對實現類進行統一的管理,如在呼叫具體實現類之前,需要列印日誌等資訊,這樣我們只需要新增一個代理類,在代理類中新增列印日誌的功能,然後呼叫實現類,這樣就避免了修改具體實現類。滿足我們所說的開閉原則。但是如果想讓每個實現類都新增列印日誌的功能的話,就需要新增多個代理類,以及代理類中各個方法都需要新增列印日誌功能(如上的代理方法中刪除,修改,以及查詢都需要新增上列印日誌的功能)
即靜態代理類只能為特定的介面(Service)服務。如想要為多個介面服務則需要建立很多個代理類。
引入動態代理:
根據如上的介紹,你會發現每個代理類只能為一個介面服務,這樣程式開發中必然會產生許多的代理類
所以我們就會想辦法可以通過一個代理類完成全部的代理功能,那麼我們就需要用動態代理
在上面的示例中,一個代理只能代理一種型別,而且是在編譯器就已經確定被代理的物件。而動態代理是在執行時,通過反射機制實現動態代理,並且能夠代理各種型別的物件
在Java中要想實現動態代理機制,需要java.lang.reflect.InvocationHandler介面和 java.lang.reflect.Proxy 類的支援
java.lang.reflect.InvocationHandler介面的定義如下:
- //Object proxy:被代理的物件
- //Method method:要呼叫的方法
- //Object[] args:方法呼叫時所需要引數
- public interface InvocationHandler {
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
- }
java.lang.reflect.Proxy類的定義如下:
- //CLassLoader loader:類的載入器
- //Class<?> interfaces:得到全部的介面
- //InvocationHandler h:得到InvocationHandler介面的子類的例項
- public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
動態代理:
具體實現類
- public class UserManagerImpl implements UserManager {
- public void addUser(String userId, String userName) {
- System.out.println("UserManagerImpl.addUser");
- }
- public void delUser(String userId) {
- System.out.println("UserManagerImpl.delUser");
- }
- public String findUser(String userId) {
- System.out.println("UserManagerImpl.findUser");
- return "張三";
- }
- public void modifyUser(String userId, String userName) {
- System.out.println("UserManagerImpl.modifyUser");
- }
- }
動態建立代理物件的類
- //動態代理類只能代理介面(不支援抽象類),代理類都需要實現InvocationHandler類,實現invoke方法。該invoke方法就是呼叫被代理介面的所有方法時需要呼叫的,該invoke方法返回的值是被代理介面的一個實現類
- public class LogHandler implements InvocationHandler {
- // 目標物件
- private Object targetObject;
- //繫結關係,也就是關聯到哪個介面(與具體的實現類繫結)的哪些方法將被呼叫時,執行invoke方法。
- public Object newProxyInstance(Object targetObject){
- this.targetObject=targetObject;
- //該方法用於為指定類裝載器、一組介面及呼叫處理器生成動態代理類例項
- //第一個引數指定產生代理物件的類載入器,需要將其指定為和目標物件同一個類載入器
- //第二個引數要實現和目標物件一樣的介面,所以只需要拿到目標物件的實現介面
- //第三個引數表明這些被攔截的方法在被攔截時需要執行哪個InvocationHandler的invoke方法
- //根據傳入的目標返回一個代理物件
- return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
- targetObject.getClass().getInterfaces(),this);
- }
- //關聯的這個實現類的方法被呼叫時將被執行
- /*InvocationHandler介面的方法,proxy表示代理,method表示原物件被呼叫的方法,args表示方法的引數*/
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- System.out.println("start-->>");
- for(int i=0;i<args.length;i++){
- System.out.println(args[i]);
- }
- Object ret=null;
- try{
- /*原物件方法呼叫前處理日誌資訊*/
- System.out.println("satrt-->>");
- //呼叫目標方法
- ret=method.invoke(targetObject, args);
- /*原物件方法呼叫後處理日誌資訊*/
- System.out.println("success-->>");
- }catch(Exception e){
- e.printStackTrace();
- System.out.println("error-->>");
- throw e;
- }
- return ret;
- }
- }
被代理物件targetObject通過引數傳遞進來,我們通過targetObject.getClass().getClassLoader()獲取ClassLoader物件,然後通過targetObject.getClass().getInterfaces()獲取它實現的所有介面,然後將targetObject包裝到實現了InvocationHandler介面的LogHandler物件中。通過newProxyInstance函式我們就獲得了一個動態代理物件。
客戶端程式碼
- public class Client {
- public static void main(String[] args){
- LogHandler logHandler=new LogHandler();
- UserManager userManager=(UserManager)logHandler.newProxyInstance(new UserManagerImpl());
- //UserManager userManager=new UserManagerImpl();
- userManager.addUser("1111", "張三");
- }
- }
可以看到,我們可以通過LogHandler代理不同型別的物件,如果我們把對外的介面都通過動態代理來實現,那麼所有的函式呼叫最終都會經過invoke函式的轉發,因此我們就可以在這裡做一些自己想做的操作,比如日誌系統、事務、攔截器、許可權控制等。這也就是AOP(面向切面程式設計)的基本原理。
插曲:
AOP(AspectOrientedProgramming):將日誌記錄,效能統計,安全控制,事務處理,異常處理等程式碼從業務邏輯程式碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的程式碼---解耦。
針對如上的示例解釋:
我們來看上面的UserManagerImplProxy類,它的兩個方法System.out.println("start-->addUser()")和System.out.println("success-->addUser()"),這是做核心動作之前和之後的兩個擷取段,正是這兩個擷取段,卻是我們AOP的基礎,在OOP裡,System.out.println("start-->addUser()")、核心動作、System.out.println("success-->addUser()")這個三個動作在多個類裡始終在一起,但他們所要完成的邏輯卻是不同的,如System.out.println("start-->addUser()")裡做的可能是許可權的判斷,在所有類中它都是做許可權判斷,而在每個類裡核心動作卻各不相同,System.out.println("success-->addUser()")可能做的是日誌,在所有類裡它都做日誌。正是因為在所有的類裡,核心程式碼之前的操作和核心程式碼之後的操作都做的是同樣的邏輯,因此我們需要將它們提取出來,單獨分析,設計和編碼,這就是我們的AOP思想。一句話說,AOP只是在對OOP的基礎上進行進一步抽象,使我們的類的職責更加單一。
動態代理優點:
動態代理與靜態代理相比較,最大的好處是介面中宣告的所有方法都被轉移到呼叫處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在介面方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。而且動態代理的應用使我們的類職責更加單一,複用性更強
總結:
其實所謂代理,就是一個人或者一個機構代表另一個人或者另一個機構採取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個物件,而代理物件可以在客戶端和目標物件之前起到中介的作用。
代理物件就是把被代理物件包裝一層,在其內部做一些額外的工作,比如使用者需要上facebook,而普通網路無法直接訪問,網路代理幫助使用者先翻牆,然後再訪問facebook。這就是代理的作用了。
縱觀靜態代理與動態代理,它們都能實現相同的功能,而我們看從靜態代理到動態代理的這個過程,我們會發現其實動態代理只是對類做了進一步抽象和封裝,使其複用性和易用性得到進一步提升而這不僅僅符合了面向物件的設計理念,其中還有AOP的身影,這也提供給我們對類抽象的一種參考。關於動態代理與AOP的關係,個人覺得AOP是一種思想,而動態代理是一種AOP思想的實現!