根據實例簡單理解靜態代理和動態代理

引入

代理模式的概念:

使用代理模式創建代理對象,讓代理對象控制目標對象的訪問,並且可以在不改變目標對象的情況下添加一些其他功能。

爲了更好的實現 高內聚,低耦合,我們通常不願意去修改已經寫好的類或方法,而有時候需要加上一些通用的功能,比如 打印日誌之類的。

簡單案例
實現用戶登錄時,打印出用戶登錄的時間(日誌)

靜態代理

目標類UserServiceImpl

public class UserServiceImpl implements UserService{
   public boolean login(String userName,String password) {
      if(userName.equals("sky") && password.equals("123")) {
         System.out.println("sky登錄成功");
         return true;
      }else {
         return false;
      }
   }
}

接口UserService

public interface UserService {
   public boolean login(String userName, String password);
}

代理類UserProxy

public class UserProxy implements UserService{
   
   private UserService userService;
   
   public UserService getUserService() {
      return userService;
   }

   public void setUserService(UserService userService) {
      this.userService = userService;
   }
   @Override
   public boolean login(String userName, String password) {
      // TODO Auto-generated method stub
      System.out.println("sky"+new Date().toLocaleString()+"登錄");
      return this.userService.login(userName, password);
   }

}

測試類Test

public class Test {
   public static void main(String[] args) {
      // TODO Auto-generated method stub
      UserProxy userProxy = new UserProxy();
      //注入目標類
      userProxy.setUserService(new UserServiceImpl());
      userProxy.login("sky", "123");
   }
}

運行結果

sky2020-4-10 21:15:01登錄
sky登錄成功

可見我們並沒有修改UserServiceImpl裏的login方法,但仍然實現了日誌的打印。


這裏從測試類簡單回溯一下:
首先我們new了一個代理對象userProxy,然後我們又new了一個目標對象並把它注入給了代理對象。
然後執行了代理對象的login方法。
再看代理類,他和目標類都實現了相同的接口,並且代理類在重寫login方法的時候return了目標類的login方法
,這就好比同一個方法重寫了兩次,一次是目標方法的實現,一個則是代理方法的“錦上添花”。

這樣既沒有破壞原方法,也給其添加了一些新功能。

但靜態代理也有很大的侷限性,就是目標函數太多時,要給每一個目標類都配上代理類,顯然是個大工程


此時動態代理就顯得方便很多

動態代理

動態代理是根據目標對象動態的生成代理對象。

目標類(UserServiceImpl)和接口(UserService)都和上述一樣

這裏直接給出動態代理對象生成器類LoggerHandler

其實就是日誌處理器,這樣一來,所有想實現日誌打印的都可以藉助此類動態生成自己的代理對象,並實現功能。

public class LoggerHandler implements InvocationHandler{
   //可以接收對象(目標)
   private Object delegate;
   //代理對象的創建
   public Object bind(Object delegate) {//獲得目標對象並給其創建對應的代理對象
      this.delegate = delegate;
      return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
      
   }
   /**
    * method 你要調用的方法
    * args 方法對應的參數
    * resul 該方法的返回值
    */
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // TODO Auto-generated method stub
      Object result = null;
      System.out.println("方法名是:"+method.getName());
      result = method.invoke(delegate, args);
      //在代理類裏增加功能,,此處增加的 日誌 功能
      System.out.println("日誌 : " + args[0] + " at " + new Date().toLocaleString() + "登錄");
      
      return result;
   }
}

測試類Test

public class Test {
   public static void main(String[] args) {
      // TODO Auto-generated method stub
      LoggerHandler loggerHandler = new LoggerHandler();
      //傳入目標對象,,要用共同的接口
      UserService userService = (UserService) loggerHandler.bind(new UserServicelmpl());
      userService.login("sky", "123");
   }
}

運行結果

方法名是:login
sky登錄成功!
日誌 : sky at 2020-4-10 21:33:49登錄

同樣簡單回溯一下:
首先測試類(Test)還是先把日誌生成器(LoggerHandler)new出來一個對象。


然後還是類似地將目標對象注入(bind方法,可以說是綁定),但對應的代理對象是用接口(User Service)接收的。

再看LoggerHandler
首先他實現了InvocationHandler接口

JDK1.3之後加入了實現動態代理的API
InvocationHandler接口
使用靜態方法Proxy.newProxyInstance()建立一個代理對象
利用invoke()方法來操作代理方法

這裏針對一下Proxy.newProxyInstance()方法

Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);

先說第二個參數:目標對象.獲得類.接口加載器
看到這就不難理解爲什麼要用 接口 接收對應的代理對象了,我猜這裏面可能利用了反射機制(瞎猜的)


再看第二個參數:目標對象.獲得類.類加載器,,
接着看invoke方法裏的第二個參數 Method,,

Method不是反射裏常見的方法嗎?這難道?

於是點進了InvocationHandler接口,然後又懵逼的出來了。

然後在newProxyInstance方法裏找了一個間接的證據
裏面有反射常用的方法

菜雞(我):說實話,這真的沒搞懂,希望有會的大佬能告訴我一下,感謝。
在這裏插入圖片描述

關於反射的一些簡單理解可以參考一下這篇文章:結合實例理解反射


再看newProxyInstance的第三個參數,這裏是把自身傳過去了。
鑑於剛纔點進了InvocationHandler接口,所以有了自己的一點理解。
接口裏很空,很多註釋,還有一個空方法!(invoke)
因爲動態代理要服務於不同的目標對象,每個目標對象各有不同,也會出現不同的代理對象,

所以這裏的this應該是幫助目標對象更好的找到自己的代理對象吧。(猜測)

最後總結一下動態代理和靜態代理的優缺點:

動態代理解決了靜態代理難以複用的缺點,但在性能上有所折扣。

That’s all,,.Thank you !!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章