1. 程式人生 > >設計模式之代理模式(java實現)

設計模式之代理模式(java實現)

代理模式(Proxy):結構型的設計模式,目的是為其他物件提供一種代理以控制對這個物件的訪問。

即,它的思想是控制類或者介面對外的功能。

代理模式分為靜態代理模式和動態代理模式兩種。

在Spring中代理模式常見的是在AOP模組中,比如 JdkDynamicAopProxy 和 Cglib2AopProxy。

先說說靜態代理。

舉一個生活化的例子,現在有一個介面Person,兒子類(Son)實現person介面,person裡面有四個方法,找工作(findJob()),租房子(rent()),找物件(findLove()),購物(buy()),現在兒子需要找物件,但他沒有時間,他的父親(Father類)接代理了這個功能,幫他物色物件。

下面是類圖:

 具體實現:

Person介面和兒子類:

package Proxy.StaticProxy;

public interface Person {
    public void findLove();
    public void rent();

    public void buy();

    public void findJob();
}
package Proxy.StaticProxy;

public class Son implements Person {
    public void findLove(){
        System.out.println("膚白貌美");
        System.out.println("身高160");
    }

    public void rent(){

    };

    public void buy(){

    };

    public void findJob(){
        System.out.println("月薪2W");
        System.out.println("世界500強");

    };
}

父親類:

package Proxy.StaticProxy;

public class Father {
    private Person person;

    //沒辦法擴充套件
    public Father(Person person){
        this.person = person;
    }

    //目標物件的引用給拿到
    public void findlove(){
        System.out.println("根據你的要求物色");
        this.person.findLove();
        System.out.println("雙方父母是不是同意");
    }

}

測試類:

package Proxy.StaticProxy;

public class StaticProxyTest {
    public static void main(String[] args) {

        Father father = new Father(new Son());
        father.findlove();
    }
}

上面是測試執行結果。 可以發現靜態代理的擴充套件會比較麻煩,如果需要擴充套件的話就需要在代理類中寫新的方法。

所以衍生了動態代理的方式。動態代理有兩種實現方式,一種是通過java反射reflect包的Proxy方法,一種是通過第三方依賴包cglib。

這裡先說下Proxy方法。

通過Proxy方法實質上是通過Proxy先生成一個臨時java檔案,然後生成對應的.class檔案,這個檔案裡面具體的目的就是實現所要代理類的各種指向的方法。

我這邊先舉例吧。依舊是以上的例子,不過我們現在選擇的是代理找工作這個方法,建立一個JDK58的類,它是專門負責找工作的。

JDK58:

package Proxy.Jdk;

import Proxy.StaticProxy.Person;

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

public class JDK58 implements  InvocationHandler {
    private Person target;

    public Object getInstance(Person target) throws Exception{
        this.target = target;

        Class<?> clazz = target.getClass();

        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(), this);

    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是58:我要給你找工作,現在已經拿到你的需求");
        System.out.println("開始物色");

        method.invoke(this.target,args);

        System.out.println("如果合適的話,就準備面試");

        return  null;
    }
}

增加測試類:

package Proxy.Jdk;

import Proxy.StaticProxy.Person;
import Proxy.StaticProxy.Son;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;

public class JDK58Test {
    public static void main(String[] args) {
        try {
            Person obj = (Person)new JDK58().getInstance(new Son());
            System.out.println(obj.getClass());
            obj.findJob();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

雖然用了代理Proxy,但我們並不知道它幹了什麼,所以,我們用一個反編譯的方法, ProxyGenerator,來看看它到底生成了什麼。

修改上面的測試類:

package Proxy.Jdk;

import Proxy.StaticProxy.Person;
import Proxy.StaticProxy.Son;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;

public class JDK58Test {
    public static void main(String[] args) {
        try {
            Person obj = (Person)new JDK58().getInstance(new Son());
            System.out.println(obj.getClass());
            obj.findJob();

            //原理:
            //1、拿到被代理物件的引用,並且獲取到它的所有的介面,反射獲取
            //2、JDK Proxy類重新生成一個新的類、同時新的類要實現被代理類所有實現的所有的介面
            //3、動態生成Java程式碼,把新加的業務邏輯方法由一定的邏輯程式碼去呼叫(在程式碼中體現)
            //4、編譯新生成的Java程式碼.class
            //5、再重新載入到JVM中執行
            //以上這個過程就叫位元組碼重組

            //JDK中有個規範,只要要是$開頭的一般都是自動生成的

            //通過反編譯工具可以檢視原始碼
            byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
            FileOutputStream os = new FileOutputStream("/Users/****/Documents/projects/Patterns/projectStudy/out/production/projectStudy/Proxy/$Proxy0.class");//這裡是要存放生成class檔案的地址,我這邊是mac電腦,所以是這樣的地址,****是自己電腦的名字,如果是windows電腦的話,那麼應該帶有碟符,例如E://
            os.write(bytes);
            os.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
執行之後,在生成的target包裡面可以找到一個$Proxy0.class檔案,在IDE裡面開啟可以看到是以下這樣的:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import Proxy.StaticProxy.Person;
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 Person {
    private static Method m1;
    private static Method m5;
    private static Method m4;
    private static Method m2;
    private static Method m6;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void rent() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void findLove() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void buy() throws  {
        try {
            super.h.invoke(this, m6, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void findJob() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m5 = Class.forName("Proxy.StaticProxy.Person").getMethod("rent");
            m4 = Class.forName("Proxy.StaticProxy.Person").getMethod("findLove");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m6 = Class.forName("Proxy.StaticProxy.Person").getMethod("buy");
            m3 = Class.forName("Proxy.StaticProxy.Person").getMethod("findJob");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

這裡會涉及到一部分類載入器和反射的知識。可以看到,但用Proxy代理之後,它會生成一個類,這個類裡面實現有person類裡面的所有方法,外加一個hashcode的方法,所以,當呼叫具體方法的時候,也就直接指向過去了。這個也就是代理模式的思想了。

cglib的話,實質和proxy差不太多,可以參考這篇文章CGLIB(Code Generation Library) 介紹與原理