Java三種代理模式

本文轉自:https://mp.weixin.qq.com/s/nBmbNP2mR7ei-lDsuOxjWghtml

代理模式

代理(Proxy)是一種設計模式,提供了對目標對象另外的訪問方式;即經過代理對象訪問目標對象.java

這樣作的好處是:能夠在目標對象實現的基礎上,加強額外的功能操做,即擴展目標對象的功能.編程

這裏使用到編程中的一個思想:不要隨意去修改別人已經寫好的代碼或者方法,若是需改修改,能夠經過代理的方式來擴展該方法.設計模式

舉個例子來講明代理的做用:假設咱們想邀請一位明星,那麼並非直接鏈接明星,而是聯繫明星的經紀人,來達到一樣的目的.明星就是一個目標對象,他只要負責活動中的節目,而其餘瑣碎的事情就交給他的代理人(經紀人)來解決.這就是代理思想在現實中的一個例子.框架

用圖表示以下:
ide

代理模式的關鍵點是:代理對象與目標對象.代理對象是對目標對象的擴展,並會調用目標對象.函數

1.1.靜態代理

靜態代理在使用時,須要定義接口或者父類,被代理對象與代理對象一塊兒實現相同的接口或者是繼承相同父類.工具

下面舉個案例來解釋:性能

模擬保存動做,定義一個保存動做的接口:IUserDao.java,而後目標對象實現這個接口的方法UserDao.java,此時若是使用靜態代理方式,就須要在代理對象(UserDaoProxy.java)中也實現IUserDao接口.調用的時候經過調用代理對象的方法來調用目標對象.測試

須要注意的是,代理對象與目標對象要實現相同的接口,而後經過調用相同的方法來調用目標對象的方法.

代碼示例:

接口:IUserDao.java

/**
* 接口
*/
public interface IUserDao {
   void save();
}

目標對象:UserDao.java

/**
* 接口實現
* 目標對象
*/
public class UserDao implements IUserDao {
   public void save() {
       System.out.println("----已經保存數據!----");
   }
}

代理對象:UserDaoProxy.java

/**
* 代理對象,靜態代理
*/
public class UserDaoProxy implements IUserDao{
   //接收保存目標對象
   private IUserDao target;
   public UserDaoProxy(IUserDao target){
       this.target=target;
   }

   public void save() {
       System.out.println("開始事務...");
       target.save();//執行目標對象的方法
       System.out.println("提交事務...");
   }
}

測試類:App.java

/**
* 測試類
*/
public class App {
   public static void main(String[] args) {
       //目標對象
       UserDao target = new UserDao();

       //代理對象,把目標對象傳給代理對象,創建代理關係
       UserDaoProxy proxy = new UserDaoProxy(target);

       proxy.save();//執行的是代理的方法
   }
}

靜態代理總結:

1.能夠作到在不修改目標對象的功能前提下,對目標功能擴展.
2.缺點:

由於代理對象須要與目標對象實現同樣的接口,因此會有不少代理類,類太多.同時,一旦接口增長方法,目標對象與代理對象都要維護.

如何解決靜態代理中的缺點呢?答案是可使用動態代理方式

1.2.動態代理

動態代理,是一種經過運行時操做字節碼,以達到加強類的功能的技術,也是Spring AOP操做的基礎。

動態代理,如今主要是用於加強類的功能,同時因爲是具備動態性,因此避免了須要頻繁建立類的操做,同時,也使得原有的代碼在不須要改變的狀況下,對類的功能進行加強。

主要的動態代理技術有:

  • 經過實現目標接口,重寫其方法,以加強其能力,典型的以JDK動態代理爲表明;
  • 經過繼承類,重寫其方法以加強其能力,典型的以CGLib爲表明

基於JDK的動態代理技術須要爲其提供接口,基於CGLib的動態代理技術不能爲final以及private修飾的方法進行加強,在使用的時候須要根據具體進行進行合理選擇。
CGLib動態代理技術使用的是繼承該類的方式,從而避免了須要接口的缺陷

使用CGLib動態代理技術能夠在不須要實現接口的狀況下動態爲對象建立代理對象,在很大程度上彌補了JDK動態代理技術的缺點,不過因爲CGLib動態代理技術是採用繼承目標類的方式,因此也存在一些問題,好比說,對於final以及private修飾的方法是沒法爲其加強的,這裏還須要注意一下。

動態代理有如下特色:
1.代理對象,不須要實現接口
2.代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象(須要咱們指定建立代理對象/目標對象實現的接口的類型)
3.動態代理也叫作:JDK代理,接口代理

JDK中生成代理對象的API

代理類所在包:java.lang.reflect.Proxy

JDK實現代理只須要使用newProxyInstance方法,可是該方法須要接收三個參數,完整的寫法是:

static Object newProxyInstance(ClassLoader loader, 
            Class[] interfaces,InvocationHandler h )

注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次爲:

  • ClassLoader loader,:指定當前目標對象使用類加載器,獲取加載器的方法是固定的
  • Class[] interfaces,:目標對象實現的接口的類型,使用泛型方式確認類型
  • InvocationHandler h:事件處理,執行目標對象的方法時,會觸發事件處理器的方法,會把當前執行目標對象的方法做爲參數傳入

代碼示例:

接口類IUserDao.java以及接口實現類,目標對象UserDao是同樣的,沒有作修改.在這個基礎上,增長一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,而後在測試類(須要使用到代理的代碼)中先創建目標對象和代理對象的聯繫,而後代用代理對象的中同名方法.點擊這裏有一份動態代理模式代碼實戰。

代理工廠類:ProxyFactory.java

/**
* 建立動態代理對象
* 動態代理不須要實現接口,可是須要指定接口類型
*/
public class ProxyFactory{

   //維護一個目標對象
   private Object target;
   public ProxyFactory(Object target){
       this.target=target;
   }

  //給目標對象生成代理對象
   public Object getProxyInstance(){
       return Proxy.newProxyInstance(
               target.getClass().getClassLoader(),
               target.getClass().getInterfaces(),
               new InvocationHandler() {
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       System.out.println("開始事務2");
                       //執行目標對象方法
                       Object returnValue = method.invoke(target, args);
                       System.out.println("提交事務2");
                       return returnValue;
                   }
               }
       );
   }

}

測試類:App.java

/**
* 測試類
*/
public class App {
   public static void main(String[] args) {
       // 目標對象
       IUserDao target = new UserDao();
       // 【原始的類型 class cn.itcast.b_dynamic.UserDao】
       System.out.println(target.getClass());

       // 給目標對象,建立代理對象
       IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
       // class $Proxy0   內存中動態生成的代理對象
       System.out.println(proxy.getClass());

       // 執行方法   【代理對象】
       proxy.save();
   }
}

總結:

代理對象不須要實現接口,可是目標對象必定要實現接口,不然不能用動態代理

1.3.Cglib代理

上面的靜態代理和動態代理模式都是要求目標對象是實現一個接口的目標對象,可是有時候目標對象只是一個單獨的對象,並無實現任何的接口,這個時候就可使用以目標對象子類的方式類實現代理,這種方法就叫作:Cglib代理

CGLIB 用來對沒有實現接口的類,實現代理對象的建立,原理是:生成一個子類繼承這個類,這個子類就是代理對象,在這個子類中實現AOP的思想

Cglib代理,也叫做子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展.

JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口,若是想代理沒有實現接口的類,就可使用Cglib實現.

Cglib是一個強大的高性能的代碼生成包,它能夠在運行期擴展java類與實現java接口.它普遍的被許多AOP的框架使用,例如Spring AOP和synaop,爲他們提供方法的interception(攔截)

Cglib包的底層是經過使用一個小而塊的字節碼處理框架ASM來轉換字節碼並生成新的類.不鼓勵直接使用ASM,由於它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉.

Cglib子類代理實現方法:

1.須要引入cglib的jar文件,可是Spring的核心包中已經包括了Cglib功能,因此直接引入pring-core-3.2.5.jar便可.
2.引入功能包後,就能夠在內存中動態構建子類
3.代理的類不能爲final,不然報錯
4.目標對象的方法若是爲final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法.

代碼示例:

目標對象類:UserDao.java

/**
* 目標對象,沒有實現任何接口
*/
public class UserDao {

   public void save() {
       System.out.println("----已經保存數據!----");
   }
}

Cglib代理工廠:ProxyFactory.java

/**
* Cglib子類代理工廠
* 對UserDao在內存中動態構建一個子類對象
*/
public class ProxyFactory implements MethodInterceptor{
   //維護目標對象
   private Object target;

   public ProxyFactory(Object target) {
       this.target = target;
   }

   //給目標對象建立一個代理對象
   public Object getProxyInstance(){
       //1.工具類
       Enhancer en = new Enhancer();
       //2.設置父類
       en.setSuperclass(target.getClass());
       //3.設置回調函數
       en.setCallback(this);
       //4.建立子類(代理對象)
       return en.create();

   }

   @Override
   public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
       System.out.println("開始事務...");

       //執行目標對象的方法
       Object returnValue = method.invoke(target, args);

       System.out.println("提交事務...");

       return returnValue;
   }
}

測試類:

/**
* 測試類
*/
public class App {

   @Test
   public void test(){
       //目標對象
       UserDao target = new UserDao();

       //代理對象
       UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

       //執行代理對象的方法
       proxy.save();
   }
}

在Spring的AOP編程中:

  • 若是加入容器的目標對象有實現接口,用JDK代理.
  • 若是目標對象沒有實現接口,用Cglib代理.

Java 動態代理的內部實現

如今咱們就會有一個問題:Java 是怎麼保證代理對象調用的任何方法都會調用 InvocationHandler 的 invoke() 方法的?

這就涉及到動態代理的內部實現。假設有一個接口 Subject,且裏面有 int request(int i) 方法,則生成的代理類大體以下:

public final class $Proxy1 extends Proxy implements Subject{
    private InvocationHandler h;
    private $Proxy1(){}
    public $Proxy1(InvocationHandler h){
        this.h = h;
    }
    public int request(int i){
        Method method = Subject.class.getMethod("request", new Class[]{int.class}); //建立method對象
        return (Integer)h.invoke(this, method, new Object[]{new Integer(i)}); //調用了invoke方法
    }
}

經過上面的方法就成功調用了 invoke() 方法。

動態代理與靜態代理的區別

代理分爲靜態代理和動態代理兩種。

  • 靜態代理,代理類須要本身編寫代碼寫成。
  • 動態代理,代理類經過 Proxy.newInstance() 方法生成。
  • 不論是靜態代理仍是動態代理,代理與被代理者都要實現兩樣接口,它們的實質是面向接口編程。
  • 靜態代理和動態代理的區別是在於要不要開發者本身定義 Proxy 類。
  • 動態代理經過 Proxy 動態生成 proxy class,可是它也指定了一個 InvocationHandler 的實現類。
  • 代理模式本質上的目的是爲了加強現有代碼的功能。

代理的實現分爲:

靜態代理:代理類是在編譯時就實現好的。也就是說 Java 編譯完成後代理類是一個實際的 class 文件。
動態代理:代理類是在運行時生成的。也就是說 Java 編譯完以後並無實際的 class 文件,而是在運行時動態生成的類字節碼,並加載到JVM中。

參考資料: JDK動態代理詳細介紹:http://www.cnblogs.com/xiaoluo501395377/p/3383130.html

相關文章
相關標籤/搜索