java基礎(十八)----- java動態代理原理源碼解析


關於Java中的動態代理,我們首先需要了解的是一種常用的設計模式--代理模式,而對於代理,根據創建代理類的時間點,又可以分為靜態代理和動態代理。

靜態代理

1、靜態代理

靜態代理:由程序員創建或特定工具自動生成源代碼,也就是在編譯時就已經將接口,被代理類,代理類等確定下來。在程序運行之前,代理類的.class文件就已經生成。

2、靜態代理簡單實現

  根據上面代理模式的類圖,來寫一個簡單的靜態代理的例子。我這兒舉一個比較粗糙的例子,假如一個班的同學要向老師交班費,但是都是通過班長把自己的錢轉交給老師。這里,班長就是代理學生上交班費,

班長就是學生的代理。

    首先,我們創建一個Person接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有上交班費的行為。這樣,學生上交班費就可以讓班長來代理執行。

/**
 * 創建Person接口
 * @author ChenHao
 */
public interface Person {
    //上交班費
    void giveMoney();
}

Student類實現Person接口。Student可以具體實施上交班費的動作。

public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    
    @Override
    public void giveMoney() {
       System.out.println(name + "上交班費50元");
    }
}

StudentsProxy類,這個類也實現了Person接口,但是還另外持有一個學生類對象,由於實現了Peson接口,同時持有一個學生對象,那么他可以代理學生類對象執行上交班費(執行giveMoney()方法)行為。

/**
 * 學生代理類,也實現了Person接口,保存一個學生實體,這樣既可以代理學生產生行為
 * @author ChenHao
 *
 */
public class StudentsProxy implements Person{
    //被代理的學生
    Student stu;
    
    public StudentsProxy(Student stu) {
        this.stu = stu;
    }
    
    //代理上交班費,調用被代理學生的上交班費行為
    public void giveMoney() {
        stu.giveMoney();
    }
}

下面測試一下,看如何使用代理模式:

public class StaticProxyTest {
    public static void main(String[] args) {
        //被代理的學生張三,他的班費上交有代理對象monitor(班長)完成
        Student  zhangsan = new Student("張三");
        
        //生成代理對象,並將張三傳給代理對象
        Person monitor = new StudentsProxy(zhangsan);
        
        //班長代理上交班費
        monitor.giveMoney();
    }
}

運行結果:

 

這里並沒有直接通過張三(被代理對象)來執行上交班費的行為,而是通過班長(代理對象)來代理執行了。這就是代理模式。

代理模式最主要的就是有一個公共接口(Person),一個具體的類(Student),一個代理類(StudentsProxy),代理類持有具體類的實例,代為執行具體類實例方法。上面說到,代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,可以附加多種用途。這里的間接性就是指不直接調用實際對象的方法,那么我們在代理過程中就可以加上一些其他用途。就這個例子來說,加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,通過代理模式很輕松就能辦到:

這里並沒有直接通過張三(被代理對象)來執行上交班費的行為,而是通過班長(代理對象)來代理執行了。這就是代理模式。

代理模式最主要的就是有一個公共接口(Person),一個具體的類(Student),一個代理類(StudentsProxy),代理類持有具體類的實例,代為執行具體類實例方法。上面說到,代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,可以附加多種用途。這里的間接性就是指不直接調用實際對象的方法,那么我們在代理過程中就可以加上一些其他用途。就這個例子來說,加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,通過代理模式很輕松就能辦到:

 

/**
 * 學生代理類,也實現了Person接口,保存一個學生實體,這樣既可以代理學生產生行為
 * @author ChenHao
 *
 */
public class StudentsProxy implements Person{
    //被代理的學生
    Student stu;
    
    public StudentsProxy(Student stu) {
        this.stu = stu;
    }
    
    //代理上交班費,調用被代理學生的上交班費行為
    public void giveMoney() {
        System.out.println("張三最近學習有進步!");
        stu.giveMoney();
    }
}

模式優缺點

優點

  • 1、 代理模式能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。
  • 2、 代理對象可以在客戶端和目標對象之間起到中介的作用,這樣起到了的作用和保護了目標對象的

缺點

  • 1、由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。
  • 2、 實現代理模式需要額外的工作,有些代理模式的實現非常復雜。

動態代理

1.動態代理

前面介紹了靜態代理,雖然靜態代理模式很好用,但是靜態代理還是存在一些局限性的,比如使用靜態代理模式需要程序員手寫很多代碼,這個過程是比較浪費時間和精力的。一旦需要代理的類中方法比較多,或者需要同時代理多個對象的時候,這無疑會增加很大的復雜度。

代理類在程序運行時創建的代理方式被成為動態代理。 我們上面靜態代理的例子中,代理類(studentProxy)是自己定義好的,在程序運行之前就已經編譯完成。然而動態代理,代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。

2、動態代理簡單實現

 在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過這個類和這個接口可以生成JDK動態代理類和動態代理對象。

public class DynamicProxyTest {

    interface IHello {
        void sayHello();
    }

    static class Hello implements IHello {
        @Override
        public void sayHello() {
            System.out.println("hello world");
        }
    }

    static class DynamicProxy implements InvocationHandler {

        Object originalObj;

        Object bind(Object originalObj) {
            this.originalObj = originalObj;
            return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
        }

        /**
         *Object proxy是代理的對象, Method method是IHello接口的sayHello,通過反射獲取,如下源碼中m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);  Object[] args是真實對象中調用方法的參數
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("welcome");
            return method.invoke(originalObj, args);
        }
    }

    public static void main(String[] args) {
        IHello hello = (IHello) new DynamicProxy().bind(new Hello());
        hello.sayHello();
    }
}

 

運行結果如下:

welcome 
hello world 

動態代理原理分析

上面說到,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。是因為所有被代理執行的方法,都是通過在InvocationHandler中的invoke方法調用的,所以我們只要在invoke方法中統一處理,就可以對所有被代理的方法進行相同的操作了。上述代碼里,唯一的“黑厘子”就是Proxy.newProxyInstance()方法,除此之外再沒有任何特殊之處。

在JDK動態代理中涉及如下角色:

業務接口Interface、業務實現類target、業務處理類Handler、JVM在內存中生成的動態代理類$Proxy0

動態代理原理圖:

說白了,動態代理的過程是這樣的:

  1. Proxy通過傳遞給它的參數(interfaces/invocationHandler)生成代理類$Proxy0;

  2. Proxy通過傳遞給它的參數(ClassLoader)來加載生成的代理類$Proxy0的字節碼文件;

動態代理的關鍵代碼就是Proxy.newProxyInstance(classLoader, interfaces, handler),我們跟進源代碼看看

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

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
  // 通過loader和接口,得到代理的Class對象
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
            // create proxy instance with doPrivilege as the proxy class may
            // implement non-public interfaces that requires a special permission
            return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    return newInstance(cons, ih);
                }
            });
        } else {
       // 創建代理對象的實例
            return newInstance(cons, ih);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    }
}

我們看一下newInstance方法的源代碼:

private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
    try {
        return cons.newInstance(new Object[] {h} );
    } catch (IllegalAccessException | InstantiationException e) {
        throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString());
        }
    }
}

講解完了代理類的生成源碼,我們一定想要看看代理類的代碼是什么樣的,下面提供一個生成代理類的方法供大家使用:

/**
 * 代理類的生成工具 
 * @author ChenHao
 * @since 2019-4-2 
 */
public class ProxyGeneratorUtils {

    /**
     * 把代理類的字節碼寫到硬盤上 
     * @param path 保存路徑 
     */
    public static void writeProxyClassToHardDisk(String path) {
        // 第一種方法
        // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);  

        // 第二種方法  

        // 獲取代理類的字節碼  
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.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 static void main(String[] args) {
        ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class");
    }

}  

此時就會在指定的C盤x文件夾下生成代理類的.class文件,我們看下反編譯后的結果:

package org.fenixsoft.bytecode;

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

public final class $Proxy0 extends Proxy
  implements DynamicProxyTest.IHello
{
  private static Method m3;
  private static Method m1;
  private static Method m0;
  private static Method m2;
  /**
  *注意這里是生成代理類的構造方法,方法參數為InvocationHandler類型,看到這,是不是就有點明白
  *super(paramInvocationHandler),是調用父類Proxy的構造方法。
  *父類持有:protected InvocationHandler h;
  *Proxy構造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  /**
  * 
  *這里調用代理對象的sayHello方法,直接就調用了InvocationHandler中的invoke方法,並把m3傳了進去。
  *this.h.invoke(this, m3, null);  this.h就是父類Proxy中保存的InvocationHandler實例變量
  *來,再想想,代理對象持有一個InvocationHandler對象,InvocationHandler對象持有一個被代理的對象,
  *再聯系到InvacationHandler中的invoke方法。嗯,就是這樣。
  */
  public final void sayHello()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  // 此處由於版面原因,省略equals()、hashCode()、toString()三個方法的代碼
  // 這3個方法的內容與sayHello()非常相似。

  static
  {
    try
    {
      m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);
      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]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

java.lang.reflect.Proxy

public class Proxy implements java.io.Serializable {

    protected InvocationHandler h; private Proxy() {
    }

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

這個代理類的實現代碼也很簡單,它為傳入接口中的每一個方法,以及從 java.lang.Object中繼承來的equals()、hashCode()、toString()方法都生成了對應的實現 ,並且統一調用了InvocationHandler對象的invoke()方法(代碼中的“this.h”就是父類Proxy中保存的InvocationHandler實例變量)來實現這些方法的內容,各個方法的區別不過是傳入的參數和Method對象有所不同而已,所以無論調用動態代理的哪一個方法,實際上都是在執行InvocationHandler.invoke()中的代理邏輯。

缺點

由Proxy創建的動態代理 不支持 對 實現類的代理
動態代理類都 extend Proxy類,implements了代理的interface。
由於java不能多繼承,這里已經繼承了Proxy類了,不能再繼承其他的類.
所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM