一篇文章徹底搞懂java動態代理的實現

朱清震發表於2017-12-22

網上有太多文章介紹動態代理是什麼,這裡就不介紹了,本文目的是讓大家弄懂動態代理是如何做到這些神奇的功能的。

先來一個小demo,通過這個demo來講解,動態代理需要三個類:

  1. 一個介面類;
  2. 一個實現介面的業務類;
  3. 一個生成動態代理類,並通過動態代理類來執行業務方法的測試類;

下面我們就一一實現它們。

  • 介面類ITaskService
package com.zqz.jdkproxy;

public interface ITaskService {
    public void doTask();
}
  • 實現ITaskService 介面的業務類TaskServiceImpl
package com.zqz.jdkproxy;

public class TaskServiceImpl implements ITaskService{

    public String doTask(){
        System.out.println("do task");
        return "do task";
    }

}
  • 實現動態代理
package com.zqz.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class TestMain {

    public static void main(String[] args)  {

         /** 
         * 通過Proxy.newProxyInstance()方法為我們的業務類生成動態代理類例項proxyInstance物件
         * 它需要三個引數
         * 當前的Classloader,用來載入動態生成代理類;動態代理類要實現的業務介面;InvocationHandler執行代理操作,並呼叫真正的業務類來執行業務方法;
         */  
        ITaskService proxyInstance = (ITaskService) Proxy.newProxyInstance(
            TestMain.class.getClassLoader(),  //裝載生成的動態代理類的classloader物件
            new Class[]{ITaskService.class},  //生成的動態代理類需要實現的業務介面
            new InvocationHandler(){          //InvocationHandler,呼叫代理操作,並執行真正的業務方法;
                //被代理的目標物件
                ITaskService taskService = new TaskServiceImpl();
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    try{
                        //業務方法執行前列印
                        System.out.println("before");
                        //呼叫業務方法
                        Object obj = method.invoke(taskService, args);
                        //業務方法執行後列印
                        System.out.println("after");
                        return obj;
                    } catch (Throwable e){
                        //業務方法丟擲異常列印
                        System.out.println("exception");
                        throw e;
                    } finally{
                        //業務方法執行完成列印(無論是否發生異常)
                        System.out.println("finally");
                    }
                }
            }
        );
        //使用代理物件執行代理方法
        proxyInstance.doTask();
    }
}

三個類完成了,通過執行TestMain ,就能輸出執行結果,大家先回答一個問題,以下兩種情況,控制檯會列印什麼內容?

  1. 業務方法正常執行;
  2. 業務方法丟擲異常;

如果你知道上面的答案,那麼我們一起往下探索,為什麼會有這樣的輸出結果(如果不知道,說明你還不清楚動態代理是什麼,開篇說了,本文對此不做講解,請自行度娘、谷歌,然後執行程式碼自己尋找答案);

假設你已經知道答案了,然後該如何入手呢?切入點就在Proxy.newProxyInstance()生成的動態代理類例項proxyInstance 上,如果我們拿到了它的原始碼,一切真相就浮出水面了,如何去拿呢?不賣關子,直接上程式碼:

byte[] bytes = ProxyGenerator.generateProxyClass("TaskService$proxy", new Class[]{ITaskService.class});
FileOutputStream fos = new FileOutputStream(new File("c://TaskService$proxy.class"));
fos.write(bytes);
fos.flush();
fos.close();
Object obj = new Object();

這段程式碼就是獲取動態代理生成的代理類,大家肯定看出來了,關鍵點就在ProxyGenerator.generateProxyClass()這個方法,為什麼是這個方法?如果我們去跟蹤Proxy.newProxyInstance()原始碼就會發現代理類的位元組碼最終是由這個方法構造出來的,它的執行過程非常簡單,如下:

Proxy類有一個靜態成員變數proxyClassCache,它就是代理類的快取,首先根據當前Classloader和業務類的介面類呼叫快取的get方法,查詢快取裡是否存在代理類的Class物件(這個快取是為了提高執行效率,避免每次呼叫都生成一個新的代理類),有就直接返回,如果不存在,則會呼叫Proxy內部類ProxyClassFactory的apply方法來構建並載入這個類,構建類就是呼叫了ProxyGenerator.generateProxyClass()方法來構建代理類的位元組碼,然後執行defineClass0()方法定義類(不清楚這個方法作用的童鞋,建議去檢視下Classloader載入類的過程,當然本文不理解這個地方是沒有影響,自己看著辦);

我們很容就拿到了它的位元組碼陣列,並把它生成class檔案儲存到了我們的硬碟上c://TaskService$proxy.class,然後通過反編譯工具就獲取到了它的原始碼:

import com.zqz.jdkproxy.ITaskService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class TaskService$proxy extends Proxy
  implements ITaskService
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;

  public TaskService$proxy(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final void doTask()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("com.zqz.jdkproxy.ITaskService").getMethod("doTask", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }
}

本文重點來了,我們一步一步的解析這個類

public final class TaskService$proxy extends Proxy implements ITaskService

這個代理類是final類,繼承自Proxy類,並且實現了ITaskService,我提兩個問題,帶著問題去找答案:
1. 為什麼要繼承Proxy類?
2. 為什麼要實現ITaskService介面?
我相信第二個問題,大家肯定都知道答案,我就不費口舌了,我們只去找第一個問題的答案,接著往下看:

  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;

四個Method成員變數,這又是什麼鬼?答案在程式碼最後的static塊中:

 static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("com.zqz.jdkproxy.ITaskService").getMethod("doTask", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }

M1就是Object類的equals方法;
M2就是Object類的toString方法Object類的toString方法;
M3就是我們業務介面類的doTask方法;
M4就是Object類的hashCode方法;

這裡又丟擲一個問題,為什麼要獲取這四個方法呢?

M3大家肯定是沒有疑問,業務方法就是我們要代理的方法;

那麼equals,toString,hashCode為什麼也要代理呢?其實我們想一想這三個方法的作用就知道了,如果不對這三個方法做代理,當呼叫這三個方法的時候,返回的就是新生成的代理物件的toString,equals,hashCode的方法,而不是我們原來業務物件的方法,執行結果肯定是不一致的,拿hashCode方法來說,動態生成的代理類與我們原來業務類是兩個不同的類,所以hashCode肯定也是不一樣的,這樣就和我們預期結果不一樣了,所以這三個方法也是需要代理的;

這裡還有一個問題不知道大家有沒有注意,即使我們需要代理這四個方法,為什麼要專門把它們賦給成員變數呢?原因就在於執行效率,如果熟悉反射的話,就會知道getMethod方法要做各種檢查、查詢操作,比直接呼叫方法要慢很多,所以在初始化的時候獲取到它們,並賦給成員變數,起到一個快取作用;

剩下的就是業務介面實現的方法了,他們實現程式碼基本是一模一樣,我們只拿實現的業務介面的方法來分析:

public final void doTask()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

關鍵程式碼在this.h這個物件上,它又是何方神聖,如果我們去動態代理類的父類Proxy類中一找就會發現,原來它就是Proxy中的一個成員變數:

  protected InvocationHandler h;

還記得我們在TestMain 方法裡面建立的那個InvocationHandler例項物件嗎? 這個h就是我們傳入的InvocationHandler物件的例項,在介面實現方法中,就是通過它呼叫我們自己實現的invoke方法來實現代理邏輯。

所以流程就是,生成動態代理物件繼承了Proxy類,Proxy類裡面有一個成員變數持有我們自己建立的InvocationHandler物件,動態代理類在實現了業務介面的方法中,呼叫我們在InvocationHandler物件中實現的invoke方法來實現最終的代理邏輯;

簡單吧?貌似也不是很簡單,但是也沒有我們想象中的那麼神奇,完!

相關文章