JDK動態代理解析

動態代理,這個詞在Java的世界裏面經常被提起,尤其是對於部分(這裏強調“部分”二字,因爲有做了一兩年就成大神的,實力強的令人髮指,這類人無疑是非常懂動態代理這點小伎倆的)做了一兩年新人來說,總是摸不清楚來龍去脈,一兩年是個坎,爲什麼是一兩年,才入門的新人可能對這東西沒什麼感覺,沒到這一步,做了很久開發的人顯然是明白這其中原理的,而做了一兩年的,知其然而不知其所以然,所以一兩年工作經驗的人很多是很茫然的。

  那麼,這裏就相對比較比較深入一點的介紹JDK動態代理的原理。這樣子介紹完,明白了其中的道理,我相信你會永遠記得JDK動態代理的思想。順帶一句,cglib做的事兒和JDK動態代理做的事兒從結局上來說差不多,方式不太一樣。

  1、先從JDK的源代碼說起,動態代理這部分源碼,Oracle版本和OpenJDK的源碼是不太一樣的,貌似Oracle版本最核心那點東西沒開源,但是原理都是一致的,這裏就挑選OpenJDK的。

  我們回顧一下JDK動態代理,先說宏觀原理,相信都懂,使用JDK動態代理最常見,至少對於我來說就是Spring的AOP部分,並且是AOP部分的聲明式事務部分。

  a、定義一個接口Car:

public interface Car {
    void drive(String driverName, String carName);
}

b、定義接口Car的一個實現類Audi:

public class Audi implements Car {

    @Override
    public void drive(String driverName, String carName) {
        System.err.println("Audi is driving... " + "driverName: " + driverName + ", carName" + carName);
    }
}

c、定義一個動態調用的控制器CarHandler:

public class CarHandler implements InvocationHandler {
    
    private Car car;
    
    public CarHandler(Car car) {
        this.car = car;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.err.println("before");
        method.invoke(car, args);
        System.err.println("after");
        return null;
    }

}

 d、測試類ProxyTest:

public class ProxyTest {
    
    @Test
    public void proxyTest() throws Exception {
        Car audi = (Car) Proxy.newProxyInstance(Car.class.getClassLoader(), new Class<?>[] {Car.class}, new CarHandler(new Audi()));
        audi.drive("name1", "audi");
    }
}

e、輸出結果:

before
Audi is driving... driverName: name1, carNameaudi
after

 上面這段,相信大家都懂,也明白原理,這就是所謂的知其然,但是不一定都能知其所以然。接下來就解釋下“知其所以然”。

  進入到Proxy類的newProxyInstance方法:

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

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass(loader, interfaces);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            Constructor cons = cl.getConstructor(constructorParams);
            return cons.newInstance(new Object[] { h });
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            throw new InternalError(e.toString());
        }
    }

注意這兩行:

// 創建代理類
Class<?> cl = getProxyClass(loader, interfaces);
// 實例化代理對象
Constructor cons = cl.getConstructor(constructorParams);

返回的是代理類的實例化對象。接下來的調用就很清晰了。

  那麼,JDK動態代理最核心的關鍵就是這個方法:

Class<?> cl = getProxyClass(loader, interfaces);

進入該方法,這個方法很長,前面很多都是鋪墊,在方法的最後調用了一個方法:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces);

 這個方法就是產生代理對象的方法。我們先不看前後,只關注這一個方法,我們自己來寫一個該方法:

public class ProxyTest {
    
    @SuppressWarnings("resource")
    @Test
    public void proxyTest() throws Exception {
        byte[] bs = ProxyGenerator.generateProxyClass("AudiImpl", new Class<?>[] {Car.class});
        new FileOutputStream(new File("d:/AudiImpl.class")).write(bs);
    }
}

於是,我們就在D盤裏面看到了一個叫做AudiImpl.class的文件,對該文件進行反編譯,得到下面這個類:

public final class AudiImpl extends Proxy implements Car {

    private static final long serialVersionUID = 5351158173626517207L;

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

    public AudiImpl(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }

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

    public final void drive(String paramString1, String paramString2) {
        try {
            this.h.invoke(this, m3, new Object[] { paramString1, paramString2 });
            return;
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

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

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

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals",
                    new Class[] { Class.forName("java.lang.Object") });
            m3 = Class.forName("com.mook.core.service.Car").getMethod("drive",
                    new Class[] { Class.forName("java.lang.String"), Class.forName("java.lang.String") });
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        } catch (NoSuchMethodException localNoSuchMethodException) {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}

這個時候,JDK動態代理所有的祕密都暴露在了你的面前,當我們調用drive方法的時候,實際上是把方法名稱傳給控制器,然後執行控制器邏輯。這就實現了動態代理。Spring AOP有兩種方式實現動態代理,如果基於接口編程,默認就是JDK動態代理,否則就是cglib方式,另外spring的配置文件裏面也可以設置使用cglib來做動態代理,關於二者的性能問題,網上也是衆說紛紜,不過我個人的觀點,性能都不是問題,不太需要去糾結這一點性能問題。

  事實上,如果你明白這一點,當你去閱讀mybatis源碼的時候是很有幫助的,mybatis的接口方式做方法查詢就充分利用了這裏的JDK動態代理。否則如果不明白原理,看mybatis的源碼的接口方式是很費勁的,當然了,這只是mybatis利用動態代理的冰山一角,要完全看懂mybaits源碼還有其他的許多難點,比如mybatis是以xml文件來配置sql語句的。

 

 

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