代理模式和Java中的動態代理機制

代理模式

定義

為實際對象提供一個代理,以控制對實際對象的訪問。代理類負責為委托類預處理消息,過濾消息并轉發消息,以及進行消息被委托類執行后的后續處理。(by IBM developerworks

代理模式的UML圖

DelegatePattern.png-18.3kB
DelegatePattern.png-18.3kB

代理類和委托類通常會實現相同的接口,以保證兩者能處理相同的消息,在訪問者看來兩者沒有絲毫的區別,是透明的。通過代理類這一中間層,能有效控制對委托類對象的直接訪問,也可以很好地隱藏和保護委托類對象,為實施不同的控制策略預留了空間,從而在設計上獲得了更大的靈活性。

靜態代理

代理類(如下的ProxySubject類)是在編譯時就實現好的。

package com.coderbao.reflection;

public interface Subject {
    void doSomething();
}
package com.coderbao.reflection;

public class RealSubject implements Subject {
    public void doSomething() {
        System.out.println("RealSubject doSomething()");
    }
}
package com.coderbao.reflection;

public class ProxySubject implements Subject{
    
    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject=subject;
    }
    
    @Override
    public void doSomething() {
        
        System.out.println("PreProcess the message");
        
        this.subject.doSomething();
        
        System.out.println("PostProcess the message");
        
    }

}

package com.coderbao.reflection;

public class DynamicProxyDemo {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        new ProxySubject(subject).doSomething();
    }
}

Output:

PreProcess the message
RealSubject doSomething()
PostProcess the message

生活中的代理模式

一些在線第三方購票網站,可以在上面買票、退票。代售網站賣的機票也是從各大航空公司獲取,此時這些網站就是代理,顧客發起買票請求-->網站向航空公司發起請求-->航空公司發出響應,是否購買成功-->代售網站告訴顧客。這中間的過程里,網站向航空公司發起請求時可能會拖延一會兒來賺差價,這就是代理自己對消息的預處理。如果你想退票,航空公司買的票是可以退票的,而第三方網站不支持退票,這就是攔截了你的調用。
瀏覽國外網站時翻墻使用的代理。用戶的網絡請求發給代理-->代理把請求發給服務器-->服務器把響應發給代理-->代理把響應發給用戶。

Java中的動態代理機制

在運行時創建接口的實現(create dynamic implementations of interfaces at runtime)

相關的API

java.lang.reflect.Proxy

  • static InvocationHandler getInvocationHandler(Object proxy) :獲得代理對象對應的調用處理器對象
  • static Class getProxyClass(ClassLoader loader, Class[] interfaces) :根據類加載器和接口創建代理類
  • static boolean isProxyClass(Class cl) :動態代理類是否已被創建
  • static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):有三個參數,1、用來加載動態代理類的類加載器(ClassLoader)。 2、代理類要實現的接口的數組。 3、InvocationHandler實例, 把所有方法的調用都轉到代理上,并指定代理的具體做法

java.lang.reflect.InvocationHandler

所有對動態代理對象的方法調用都會被轉向到 InvocationHandler 接口的實現上

  • Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    • 傳入 invoke()方法中的 proxy 參數是實現要代理接口的動態代理對象。通常你是不需要他的。
    • invoke()方法中的 Method 對象參數代表了被動態代理的接口中要調用的方法,從這個 method 對象中你可以獲取到這個方法名字,方法的參數,參數類型等等信息。
    • Object 數組參數包含了被動態代理的方法需要的方法參數。注意:原生數據類型(如int,long等等)要傳入等價的包裝對象(如Integer, Long等等)

使用方式

// Step1、實現InvocationHandler接口創建自定義處理器
InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //在調用委托對象的方法之前,可以執行一些預處理
        System.out.println("PreProcess the message");
        
        //調用委托對象的方法
        subject.doSomething();
        subject.doSomething();
        
        //在調用委托對象的方法之后,可以執行一些收尾處理
        System.out.println("PostProcess the message");
        return null;
    }
};

// 創建動態代理類的實例,封裝了下節中的Step2、3、4
Subject proxy = (Subject) Proxy.newProxyInstance(
        Subject.class.getClassLoader(), new Class[] { Subject.class },
        handler);
proxy.doSomething();

Output:

PreProcess the message
RealSubject doSomething()
RealSubject doSomething()
PostProcess the message

源碼實現

動態類的實例生成

Proxy 的重要靜態變量

   /** maps a class loader to the proxy class cache for that loader 
   * 映射表:用于維護類加載器對象到其對應的代理類緩存
   */
    private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache
        = new WeakHashMap<>();

    /** marks that a particular proxy class is currently being generated
    * 標記一個動態代理類正在被創建中
    */
    private static Object pendingGenerationMarker = new Object();

    /** set of all generated proxy classes, for isProxyClass implementation
    * 同步表:記錄已經被創建的動態代理類類型,供方法 isProxyClass 進行相關的判斷
    */
    private static Map<Class<?>, Void> proxyClasses =
        Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());
        
    /** 所有代理類名字的前綴,均為$Proxy */
    private final static String proxyClassNamePrefix = "$Proxy";
    
    /** 用于每個獨特的代理類的名字生成的下一個數字*/
    private static long nextUniqueNumber = 0;
        
    /**
     * 代理類實例關聯的調用處理器引用
     * @serial
     */
    protected InvocationHandler h;

Proxy的帶參構造函數

    protected Proxy(InvocationHandler h) {
        doNewInstanceCheck();
        this.h = h;
    }

Proxy的靜態方法newProxyInstance,生成動態代理類的實例

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        final SecurityManager sm = System.getSecurityManager();
        //對這組接口interfaces進行安全檢查,檢查接口類對象是否對類加載器可見,接口長度限制,數組中是否均為接口,是否有重復接口等
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
        }

        /*
         * Step2、根據指定類裝載器和一組接口,生成代理類的類對象,或從緩存中查找
         */
        Class<?> cl = getProxyClass0(loader, interfaces);

        /*
         * Step3、通過反射獲取上文提及的構造函數對象
         */
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //Step4、生成代理類的實例
                return newInstance(cons, ih);//即return cons.newInstance(new Object[] {h} );
            //-------------------------------------------
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

動態類的代碼生成

getProxyClass0(loader, interfaces)方法調用ProxyGenerator的 generateProxyClass方法生成代理類的代碼

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //確定代理類的名字,為包名+$ProxyN,N為0,1,2,3,...                                   
        long num;
        synchronized (nextUniqueNumberLock) {
            num = nextUniqueNumber++;
        }
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        /*
         * 生成代理類
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces);
        try {
            proxyClass = defineClass0(loader, proxyName,
                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }   

sun.misc.ProxyGenerator為我們代勞了寫些套路化的代碼(OpenJDK源碼下載點這里,導入需要的ProxyGenerator.Java文件)

    public static byte[] generateProxyClass(final String name,Class<?>[] interfaces) {
        return generateProxyClass(name, interfaces, (ACC_PUBLIC | ACC_FINAL | ACC_SUPER));
    }

    public static byte[] generateProxyClass(final String name,
                                            Class<?>[] interfaces,
                                            int accessFlags)  {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
       //動態生成代理類的字節碼  
       final byte[] classFile = gen.generateClassFile();  
  
    // 如果saveGeneratedFiles為true,則會把生成的代理類的字節碼保存到硬盤上  
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }  
  
    // 返回代理類的字節碼  
       return classFile;  
   }  

獲取動態類的字節碼

我們導入sun.misc.ProxyGenerator.java后,調用generateProxyClass可以生成動態類的字節碼,并將其保存到硬盤上。
這里可以自定義生成類的名字為DynamicProxyType。

package com.coderbao.reflection;

import java.io.FileOutputStream;
import java.io.IOException;

import sun.misc.ProxyGenerator;

public class ProxyGeneratorUtils {
    /** 
     * 把代理類的字節碼寫到硬盤上 
     * @param path 保存路徑 
     */  
    public static void writeProxyClassToDisk(String path) {  
          
        // 獲取代理類的字節碼  
        byte[] classFile = ProxyGenerator.generateProxyClass("DynamicProxyType", RealSubject.class.getInterfaces());  
          
        FileOutputStream out = null;  
          
        try {  
            out = new FileOutputStream(path);  
            out.write(classFile);  
            out.flush();  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            try {  
                out.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}
public class DynamicProxyDemo {
    public static void main(String[] args) {
        //在F盤根目錄下生成了Proxy1.class,拖到AndroidStudio中反編譯即可
        ProxyGeneratorUtils.writeProxyClassToDisk("F:/DynamicProxyType.class");  
    }
    
    
}

生成的動態類的代碼


import com.coderbao.reflection.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class DynamicProxyType extends Proxy implements Subject {
        private static Method m1;
        private static Method m0;
        private static Method m3;
        private static Method m2;

        //構造方法,參數就是一開始實例化的InvocationHandler接口的實例 
        public $Proxy1(InvocationHandler var1) throws  {
                super(var1);
        }

        public final boolean equals(Object var1) throws  {
                try {
                        return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
                } catch (RuntimeException | Error var3) {
                        throw var3;
                } catch (Throwable var4) {
                        throw new UndeclaredThrowableException(var4);
                }
        }

        public final int hashCode() throws  {
                try {
                        return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
                } catch (RuntimeException | Error var2) {
                        throw var2;
                } catch (Throwable var3) {
                        throw new UndeclaredThrowableException(var3);
                }
        }

        public final void doSomething() throws  {
                try {
                        super.h.invoke(this, m3, (Object[])null);
                } catch (RuntimeException | Error var2) {
                        throw var2;
                } catch (Throwable var3) {
                        throw new UndeclaredThrowableException(var3);
                }
        }

        public final String toString() throws  {
                try {
                        return (String)super.h.invoke(this, m2, (Object[])null);
                } catch (RuntimeException | Error var2) {
                        throw var2;
                } catch (Throwable var3) {
                        throw new UndeclaredThrowableException(var3);
                }
        }

        //static代碼塊加載Method對象
        static {
                try {
                        m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                        m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                        m3 = Class.forName("com.coderbao.reflection.Subject").getMethod("doSomething", new Class[0]);
                        m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                } catch (NoSuchMethodException var2) {
                        throw new NoSuchMethodError(var2.getMessage());
                } catch (ClassNotFoundException var3) {
                        throw new NoClassDefFoundError(var3.getMessage());
                }
        }
}

由代碼可見,調用動態代理類的實例的方法時,會轉發給InvocationHandler實現類的invoke方法處理。InvocationHandler實現類的invoke方法會代理Object類的equals、hashCode、toString方法和指定接口的所有方法。

動態代理的性能消耗和利弊

性能消耗

原因: 使用反射來分發方法的消耗(There is probably some performance cost because of dispatching methods reflectively instead of using the built-in virtual method dispatch)
Debunking myths: proxies impact performance (50倍)
Java theory and practice: Decorating with dynamic proxies(less than a factor of two)
Benchmarking the cost of dynamic proxies(factor of 1.63 in raw use)
總結:

  • 如果被代理的對象要執行重量級的耗時操作(數據庫或文件讀寫或事務管理),那么動態代理增加的性能消耗可以忽略
  • 如果的確需要優化性能,可使用字節碼生成工具(a byte code weaving approach),如AspectJ
  • 動態代理的操作次數不宜過多

不足

從設計上看動態代理類要繼承Proxy類,而Java中沒有多繼承,所以只能對接口創建動態代理類,不能代理抽象類。此外,還有一些歷史遺留的類,它們將因為沒有實現任何接口而從此與動態代理永世無緣。

優點

可以充當接口的Decorator或Proxy,減少書寫重復代碼

實際用途

Database Connection and Transaction Management(數據庫的連接與事務管理)

Spring 框架中有一個事務代理可以讓你提交/回滾一個事務。它的具體原理在 Advanced Connection and Transaction Demarcation and Propagation 一文中有詳細描述,,方法調用序列的大意如下:

  web controller --> proxy.execute(...);
  proxy --> connection.setAutoCommit(false);
  proxy --> realAction.execute();
  realAction does database work
  proxy --> connection.commit();

Dynamic Mock Objects for Unit Testing(單元測試中的動態Mock對象)

Butterfly Testing工具利用動態代理來實現dynamic stub,mock 和代理類,從而進行單元測試。在測試類A的時候,如果類A用到了類B(B實際上是接口),你可以傳一個B 接口的 mock 實現給 A ,來代替實際的 B 接口實現。所有對接口B的方法調用都會被記錄,你可以自己設置 B 的 mock 中方法的返回值。
而且 Butterfly Testing 工具允許你在 B 的 mock 中包裝真實的 B 接口實現,這樣所有調用 mock 的方法都會被記錄,然后把調用轉發到真實的 B 接口實現。這樣你就可以檢查B中方法真實功能的調用情況。例如:你在測試 DAO 時你可以把真實的數據庫連接包裝到 mock 中。DAO 可以如常地在數據庫中讀寫數據,因為mock 會把所有對數據庫的調用都轉發給數據庫,你可以通過 mock 來檢查 DAO 是不是以正確的方式來使用數據庫連接,比如是否調用了 connection.close()方法,這種情況不能通過DAO 方法的返回值來判斷。

Adaptation of DI Container to Custom Factory Interfaces(依賴注入容器到自定義工廠接口的適配器)

依賴注入容器 Butterfly Container 有個強大的特性可以讓你把整個容器注入到這個容器生成的 bean 中。但是,如果你不想依賴這個容器接口,這個容器可以按你需要地把自己適配成一個自定義的工廠接口。你只需要寫接口,不必實現它。這樣這個工廠接口和你的類看起來就像這樣:

public interface IMyFactory {
  Bean   bean1();
  Person person();
  ...
}

public class MyAction{

  protected IMyFactory myFactory= null;

  public MyAction(IMyFactory factory){
    this.myFactory = factory;
  }

  public void execute(){
    Bean bean = this.myFactory.bean();
    Person person = this.myFactory.person();
  }

}

當 MyAction 類調用通過容器注入到構造方法中的 IMyFactory 實例的方法時,這個方法調用實際先調用了 IContainer.instance()方法,這個方法可以讓你從容器中獲取實例。這樣這個對象可以把 Butterfly Container 容器在運行期當成一個工廠使用,比起在創建這個類的時候進行注入,這種方式顯然更好。而且這種方法沒有依賴到 Butterfly Container 中的任何接口。

AOP-like Method Interception(AOP中的方法攔截)

如果某個bean實現了某些接口,那么Spring 框架就能攔截對bean 的方法調用。Spring 框架使用動態代理來包裝 bean,所有對 bean 中方法的調用都會被代理攔截。代理可以決定是否要調用其他對象的方法來做預處理/攔截/后續處理。

Retrofit中自定義的網絡請求到OkHttp.Call的適配

使用GitHub github = retrofit.create(GitHub.class);時;我們就創建了一個我們自定義的接口GitHub的動態代理類,當我們發出消息時(調用github.contributors().execute()時),代理類會按照Retrofit先前配置的邏輯來處理我們發出的消息:比如交由okhttp3.Call來進行網絡請求。Retrofit完成的是封裝客戶網絡請求的高效工作,而真正的網絡請求的工作是委托給了OkHttp來完成。

Sample

package com.example.retrofit;

import java.io.IOException;
import java.util.List;
import retrofit2.Call;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.Retrofit;
import retrofit2.http.GET;
import retrofit2.http.Path;

public final class SimpleService {
  public static final String API_URL = "https://api.github.com";

  public static class Contributor {
    public final String login;
    public final int contributions;

    public Contributor(String login, int contributions) {
      this.login = login;
      this.contributions = contributions;
    }
  }
  
  public interface GitHub {
    @GET("/repos/{owner}/{repo}/contributors")
    Call<List<Contributor>> contributors(
        @Path("owner") String owner,
        @Path("repo") String repo);
  }

  public static void main(String... args) throws IOException {
    // 創建一個指向GitHub API接口的REST適配器
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(API_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

    // 創建GitHub API接口的實例
    GitHub github = retrofit.create(GitHub.class);

    // 獲取contributors,返回一個Call<List<Contributor>>對象,根據注解和方法參數配置好了url和請求參數
    Call<List<Contributor>> call = github.contributors("square", "retrofit");

    // execute()發起真正的請求,同步
    List<Contributor> contributors = call.execute().body();
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Retrofit.create

public final class Retrofit {
      public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
              private final Platform platform = Platform.get();
    
              @Override public Object invoke(Object proxy, Method method, Object... args)
                  throws Throwable {
                // method來自Object.class則不做自定義處理
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                //默認處理,即根據平臺類型(Android、Java8、IOS)做處理
                if (platform.isDefaultMethod(method)) {
                  return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                ServiceMethod serviceMethod = loadServiceMethod(method);
                OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
      }
}

References

Java Reflection - Dynamic Proxies(一個不錯的的Java知識點學習網站)
Java 動態代理機制分析及擴展,第 1 部分(IBM啊)
Java動態代理機制詳解(JDK 和CGLIB,Javassist,ASM)
retrofit

我的博客,剛剛開通不久,歡迎來捧捧人氣:http://coderbao.com/index.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java代理和動態代理機制分析和應用 概述 代理是一種常用的設計模式,其目的就是為其他對象提供一個代理以控制對某個...
    丸_子閱讀 3,060評論 6 57
  • 原文: Dyanmic Proxy Classes 介紹 一個動態代理類是實現了多個接口存在于運行時的類,這樣,一...
    半黑月缺閱讀 984評論 0 0
  • 你現在是否會有這樣的感觸? 沒有信號沒有WIFI,就慌了手腳,科技是讓你更自由,還是不自由? 在圈子里熱絡,現實中...
    酷活動閱讀 1,227評論 0 0
  • 昨天的《會有天使替我愛你》里面的女主本來也是對羿兇巴巴的,只是在他離開以后,她變得溫柔了。 也許,是在失去以后才懂...
    減肥的女孩閱讀 474評論 0 1
  • 最近參加了一個一周CP的活動,今日的任務是彼此推薦一首歌,并且語音講述推薦理由。看到任務后,我思索了一下,覺得現成...
    陳禹同Bruce閱讀 971評論 0 0