代理模式看這一篇就夠了

陳仁義發表於2018-11-13

靜態代理


代理介面
public interface Person {
    String searchHouse();
}

複製程式碼
public class MasterProxy implements Person {

    private Person person;
    public MasterProxy(Person person) {
        this.person = person;
    }

    @Override
    public String searchHouse() {
        System.out.println("我是鏈家,我幫別人找房子..");
        //第一個引數是target,也就是被代理類的物件;第二個引數是方法中的引數
        String msg =  person.searchHouse();
        System.out.println("------------------------");
        System.out.println("|          |           |");
        System.out.println("|          |           |");
        System.out.println("|          |           |");
        System.out.println("|-------  -------  -----");
        System.out.println("                       |");
        System.out.println("|----------------------|");
        System.out.println("我是鏈家,已經找到了..");
        return msg;
    }
}
複製程式碼
public class ProxyTest {
    @Test
    public void testJDKObject() {

        Person person = new Master();

        MasterProxy masterProxy = new MasterProxy(person);
        String sb = masterProxy.searchHouse();
        System.out.println(sb);
    }
}

複製程式碼

缺點:

  1. 代理類和委託類實現了相同的介面,代理類通過委託類實現了相同的方法。這樣就出現了大量的程式碼重複。
  2. 因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類,類太多.同時,一旦介面增加方法,目標物件與代理物件都要維護。增加了程式碼維護的複雜度。
  3. 靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類

動態代理的兩種方式


代理物件
public class Master implements Person{
    @Override
    public String searchHouse() {
        System.out.println("我需要找一個整租兩室一廳的房子");
        return "done";
    }

    public static String pay() {
        System.out.println("我支付了");
        return "我支付了";
    }

    public final String sign() {
        System.out.println("我簽約了");
        return "我簽約了";
    }
}

複製程式碼

JDK


/**
 * 代理物件,不需要實現介面
 *
 * 代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理
 * @param <T>
 */
public class HomeLineJDK<T> implements InvocationHandler {

    private Person target;
    public T createProxyInstance(Person target){
        this.target = target;
        Class clazz = target.getClass();
        //指定當前目標物件使用類載入器,獲取載入器的方法是固定的
        //目標物件實現的介面的型別,使用泛型方式確認型別
        //事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入
        T obj = (T)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
      //  new ProxyClass().getClazz("jdkObject");
        return obj;
    }


    public T createProxyInstance(Class clazz){
        T obj = (T)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
        new ProxyClass().getClazz("jdkClass");
        return obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //System.out.println("我是鏈家,我幫別人找房子..");
        //第一個引數是target,也就是被代理類的物件;第二個引數是方法中的引數
        Object msg =  method.invoke(target, args);
        System.out.println("我是鏈家,已經找到了..");
        return msg;
    }
}
複製程式碼
  • 代理物件,不需要實現介面
  • 代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理

cglib

/**
 * Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.
 */
public class HomeLineCglib implements MethodInterceptor {
    private Object target;

    public <T> T createProxyInstance(T targetObject){
        this.target = targetObject;  //給業務物件賦值
        Enhancer enhancer = new Enhancer(); //建立加強器,用來建立動態代理類 也就是cglib中的一個class generator
        enhancer.setSuperclass(this.target.getClass());  //為加強器指定要代理的業務類(即:為下面生成的代理類指定父類)
        //設定回撥:對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實現intercept()方法進行攔
        enhancer.setCallback(this);
        // 建立動態代理類物件並返回
        T o = (T)enhancer.create();
//        new ProxyClass().getCglibClazz(enhancer);
        return o;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("我是鏈家,我幫別人找房子..");
        //第一個引數是target,也就是被代理類的物件;第二個引數是方法中的引數
        Object msg =  methodProxy.invoke(this.target, objects);
        System.out.println("我是鏈家,已經找完了..");
        return msg;
    }
}

複製程式碼

測試:

public class InvacationTest {

    @Test
    public void testJDKObject() {
        Person person = new HomeLineJDK<Person>().createProxyInstance(new Master());
        String sb = person.searchHouse();
       // System.out.println(sb);
    }

    @Test
    public void testJDKCLass() {
        Person person1 = new HomeLineJDK<Person>().createProxyInstance(Master.class);
        String sb1 = person1.searchHouse();
        System.out.println(sb1);
    }



    @Test
    public void testCglib() {
        Master master = new Master();
        master = new HomeLineCglib().createProxyInstance(master);
        master.searchHouse();

    }

    @Test
    public void testStaticCglib() {
        Master master = new Master();
        master = new HomeLineCglib().createProxyInstance(master);
        master.pay();

    }

    @Test
    public void testFinalCglib() {
        Master master = new Master();
        master = new HomeLineCglib().createProxyInstance(master);
        master.sign();

    }
}
複製程式碼

獲取代理物件

我們可以$Proxy0獲取到代理物件的class,反編譯出生成的代理類


public class ProxyClass {

//獲取到jdk代理物件的class
    public void getClazz(String fileName){
        String path = "D://"+fileName+".class";
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Master.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();
            }
        }
    }

//獲取到cglib對應的class
    public void getCglibClazz(Enhancer enhancer){
        String path = "D://cglib.class";
        byte[] classFile = new byte[0];
        try {
            classFile = enhancer.getStrategy().generate(enhancer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        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();
            }
        }
    }

}

複製程式碼

代理物件原始碼解析:

  • jdk對應的位元組碼物件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import services.proxy.Person;

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m2;
    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 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 String searchHouse() throws  {
        try {
            return (String)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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("services.proxy.Person").getMethod("searchHouse");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製程式碼
  • cglib對應的位元組碼物件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import services.proxy.Person;

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m2;
    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 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 String searchHouse() throws  {
        try {
            return (String)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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("services.proxy.Person").getMethod("searchHouse");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

複製程式碼

兩種代理模式效能比較:

package services.proxy;

import org.junit.Test;

public class ProxyPerofrmanceTest {

    @Test
    public void testTime(){
        testCglibCreateTime(10000);
        testJdkCreateTime(10000);
       
        System.out.println("-------------------");
        testCglibExecuteTime(10);
        testCglibExecuteTime(100);
        testCglibExecuteTime(1000);
        testCglibExecuteTime(10000);
        testCglibExecuteTime(100000);
        testCglibExecuteTime(1000000);
        testCglibExecuteTime(10000000);
        System.out.println("-------------------");
        System.out.println("-------------------");
        testJdkExecuteTime(10);
        testJdkExecuteTime(100);
        testJdkExecuteTime(1000);
        testJdkExecuteTime(10000);
        testJdkExecuteTime(100000);
        testJdkExecuteTime(1000000);
        testJdkExecuteTime(10000000);
    }

   
    public void testJdkExecuteTime(int times){
        Person person = new HomeLineJDK<Person>().createProxyInstance(new Master());
        long jdks = System.currentTimeMillis();
        for (int i = 0;i<times;i++){
            person.searchHouse();
        }
        long jdke = System.currentTimeMillis();
        System.out.println("jdk"+times+"次執行方法處處理時間:"+(jdke-jdks));
    }
   
    public void testJdkCreateTime(int times){
        long jdks = System.currentTimeMillis();
        for (int i = 0;i<times;i++){
            Person person = new HomeLineJDK<Person>().createProxyInstance(new Master());
        }
        long jdke = System.currentTimeMillis();
        System.out.println("jkd建立代理物件"+times+"次處理時間:"+(jdke-jdks));
    }
}

複製程式碼

jdk和cglib第一次建立物件會將物件快取,所以我們先建立一次去掉建立物件時間

jdk cglib
2 ms 228 ms

接下來我們看執行對應方法的耗時

次數 jdk cglib
10 0 17
100 1 0
1000 0 1
10000 2 4
100000 5 4
1000000 13 9
10000000 94 44

可以看到在建立物件並快取到記憶體中的時候,cglib耗時比較jdk嚴重。但是在正直的執行過程中我們可以看到開始的時候jdk的效能優於cglib當達到一定呼叫量的時候cglib的效能較jdk要好。

相關文章