1. 程式人生 > >Java動態代理講解——深入淺出

Java動態代理講解——深入淺出

一、代理模式

        要講解動態代理,就要先說代理模式,這是一種最常用的設計模式。該模式中有以下三個角色。

        抽象角色:通過介面或抽象類宣告真實角色實現的業務方法,即被代理類與代理類都要實現的介面。

        代理角色:實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,並可以附加自己的操作。

        真實角色:實現抽象角色,定義真實所要實現的業務邏輯,供代理角色呼叫。

二、靜態代理與動態代理

       靜態代理需要為每個被代理類(真實角色)都建立特定的代理類(代理角色),這些類需要程式設計師自己編寫,這樣會帶來極大的不便。

       因此出現了動態代理,動態代理是基於JDK的反射機制。

三、動態代理的實現方式

        假如我們現在要實現一個日誌增強類,在各種類呼叫各種方法前和方法執行結束後列印日誌。LogHandler實現InvocationHandler,並且需要自己實現invoke方法,該方法中需要執行日誌列印語句和原本的業務邏輯方法。

public class LogHandler implements InvocationHandler{

    private Object target;

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

    //生成代理物件
    public Object bind(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("log before");//增強日誌語句
        method.invoke(target, args);//呼叫被代理類的業務邏輯方法
        System.out.println("log after");//增強日誌語句
        return null;
    }
}
        假如現在我們有一個User介面及其實現類UserImpl。
public interface User {
    void hello();
}
public class UserImpl implements User{

    public void hello(){
        System.out.println("hello");
    }
}

        要通過LogHandler類來獲得UserImpl的日誌代理類,怎麼辦呢?

public class Main {

    public static void main(String[] args) {
        UserImpl user = new UserImpl();
        LogHandler logHandler = new LogHandler(user);
        User proxy = (User) logHandler.bind();
        proxy.hello();
    }
}

三、實現原理

       關於到底是如何生成代理類的,我們要仔細看一下bind方法中的Proxy.newProxyInstance(......)方法,這個方法裡面有一切我們想知道的玄機。我們來看一下原始碼中最核心的一些東西(一些關於安全性合法性檢查的語句就不說了)

Class<?> cl = getProxyClass0(loader, intfs);

       引數裡的loader指的是被代理的類載入器,intfs指的是被代理類所實現的介面集合,getProxyClass0方法根據這兩個引數構造了一個類物件,該類繼承Proxy類(因此JDK動態代理只支援介面代理!),實現了被代理類所實現的介面。

       接著進入getProxyClass0方法

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
}

       這裡可以看得很清楚,如果再之前已經生成過“擁有相同介面集合的類”的代理類,則可以直接從快取中取出該代理類物件,否則通過代理類工廠建立。在獲取到代理類之後,就要建立代理物件了,通過Java的反射機制,直接通過Contructor類的newInstance方法建立並且返回。

return cons.newInstance(new Object[]{h});

        這裡new Object[]{h}是構造器引數,因為我們的代理類是繼承Proxy的,而Proxy提供了一個受保護的構造器和一個私有構造器,我們來看一下受保護的構造器。

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

        其中的引數正是InvocationHandler,因此其子類代理類一定會有一個有參構造器,且有傳入InvocationHandler物件。為什麼要傳入這個引數呢?因為Proxy有個屬性就是InvocationHandler,這個屬性有什麼用呢?我們來看一下生成的代理類的程式碼(來自網路)。

public final class $Proxy0 extends Proxy implements UserManager {  
    private static Method m1;  
    private static Method m0;  
    private static Method m3;  
    private static Method m2;  
  
    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("cn.edu.jlu.proxy.UserManager").getMethod("addUser",  
                    new Class[0]);  
            m2 = Class.forName("java.lang.Object").getMethod("toString",  
                    new Class[0]);  
        } catch (NoSuchMethodException nosuchmethodexception) {  
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
        } catch (ClassNotFoundException classnotfoundexception) {  
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
        }  
    }  
  
    public $Proxy0(InvocationHandler invocationhandler) {  
        super(invocationhandler);  
    }  
  
    @Override  
    public final boolean equals(Object obj) {  
        try {  
            return ((Boolean) super.h.invoke(this, m1, new Object[] { obj }))  
                    .booleanValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public final int hashCode() {  
        try {  
            return ((Integer) super.h.invoke(this, m0, null)).intValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public final String toString() {  
        try {  
            return (String) super.h.invoke(this, m2, null);  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public void addUser() {  
        try {  
            super.h.invoke(this, m3, null);  
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
  
    }  
}  
        可以看到,所有的方法都會呼叫super.h.invoke方法!因此,當我們呼叫代理類的很多方法時,其實都是指向了invoke方法。