說說Java代理模式

簡書 占小狼 轉載請注明原創出處,謝謝!

在平時寫代碼時,經常會用到各種設計模式,其中一種就是代理模式,代理實現可以分為靜態代理和動態代理。

靜態代理

靜態代理的模式在平時生活中也很常見,比如買火車票這件小事,黃牛相當于是火車站的代理,我們可以通過黃?;蛘叽埸c進行買票行為,但只能去火車站進行改簽和退票,因為只有火車站才有改簽和退票的方法。

在代碼實現中相當于為一個委托對象realSubject提供一個代理對象proxy,通過proxy可以調用realSubject的部分功能(買票),并添加一些額外的業務處理(收取手續費),同時可以屏蔽realSubject中未開放的接口(改簽和退票)。

1、RealSubject 是委托類,Proxy 是代理類;
2、Subject 是委托類和代理類的接口;
3、request() 是委托類和代理類的共同方法;
4、RealSubject 中有未公開的方法

具體代碼實現如下:

interface Subject {
    void request();
}

class RealSubject implements Subject {
    public void request(){
        System.out.println("RealSubject");
    }
}

class Proxy implements Subject {
    private Subject subject;
    
    public Proxy(Subject subject){
        this.subject = subject;
    }
    public void request(){
        System.out.println("begin");
        subject.request();
        System.out.println("end");
    }
}

public class ProxyTest {
    public static void main(String args[]) {
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}

靜態代理實現中,一個委托類對應一個代理類,代理類在編譯期間就已經確定。

動態代理

動態代理中,代理類并不是在Java代碼中實現,而是在運行時期生成,相比靜態代理,動態代理可以很方便的對委托類的方法進行統一處理,如添加方法調用次數、添加日志功能等等,動態代理分為jdk動態代理和cglib動態代理,下面通過一個例子看看如何實現jdk動態代理。

1、定義業務邏輯

public interface Service {  
    //目標方法 
    public abstract void add();  
} 

public class UserServiceImpl implements Service {  
    public void add() {  
        System.out.println("This is add service");  
    }  
} 

2、利用java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口定義代理類的實現。

class MyInvocatioHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-----before-----");
        Object result = method.invoke(target, args);
        System.out.println("-----end-----");
        return result;
    }
    // 生成代理對象
    public Object getProxy() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        return Proxy.newProxyInstance(loader, interfaces, this);
    }
}

3、使用動態代理

public class ProxyTest {
    public static void main(String[] args) {
        Service service = new UserServiceImpl();
        MyInvocatioHandler handler = new MyInvocatioHandler(service);
        Service serviceProxy = (Service)handler.getProxy();
        serviceProxy.add();
    }
}

執行結果:

-----before-----
This is add service
-----end-----

代理對象的生成過程由Proxy類的newProxyInstance方法實現,分為3個步驟:
1、ProxyGenerator.generateProxyClass方法負責生成代理類的字節碼,生成邏輯比較復雜,有興趣的同學可以繼續分析源碼 sun.misc.ProxyGenerator;

// proxyName:格式如 "com.sun.proxy.$Proxy.1";
// interfaces:代理類需要實現的接口數組;
// accessFlags:代理類的訪問標識;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

2、native方法Proxy.defineClass0負責字節碼加載的實現,并返回對應的Class對象。

Class clazz = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);

3、利用clazz.newInstance反射機制生成代理類的對象;

反編譯代理類
為了更清楚的理解動態代理,通過以下方式把代理類字節碼生成class文件。

byte[] classFile = ProxyGenerator.generateProxyClass("com.sun.proxy.$Proxy.1", service.getClass().getInterfaces());
FileOutputStream out = new FileOutputStream("com.sun.proxy.$Proxy.1.class");
out.write(classFile);
out.flush();

使用 反編譯工具 jad jad com.sun.proxy.$Proxy.1 看看代理類如何實現,反編譯出來的java代碼如下:

public final class $proxy1 extends Proxy implements Service {

    public $proxy1(InvocationHandler invocationhandler) {
        super(invocationhandler);
    }

    public final boolean equals(Object obj) {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void add() {
        try {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

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

    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("zzzzzz.Service").getMethod("add", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception) {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception) {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

從上述代碼可以發現:
1、生成的$proxy1繼承自Proxy類,并實現了Service接口。
2、執行代理對象的方法,其實就是執行InvocationHandle對象的invoke方法,傳入的參數分別是當前代理對象,當前執行的方法和參數。

super.h.invoke(this, m3, null);

JDK動態代理局限性
通過反射類ProxyInvocationHandler回調接口實現的jdk動態代理,要求委托類必須實現一個接口,但事實上并不是所有類都有接口,對于沒有實現接口的類,便無法使用該方方式實現動態代理。


我是占小狼
坐標魔都,白天是上班族,晚上是知識的分享者
如果讀完覺得有收獲的話,歡迎點贊加關注


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

推薦閱讀更多精彩內容