代理模式

定義

爲其它對象提供一種代理以控制這個對這個對象的訪問。

不管是靜態代理還是動態代理,目的都是要拿到目標對象的引用,並且能夠調用到目標類的業務方法。

靜態代理

  • 人的抽象接口
package com.faith.net.proxy.staticed;

/**
 * 人抽象接口
 */
public interface Person {

    public void drive();
}
  • Boss作爲被代理對象
package com.faith.net.proxy.staticed;

/**
 * 老闆, 僱傭者
 */
public class Boss implements Person {

    @Override
    public void drive() {
        System.out.println("drive..");
    }
}
  • Employee作爲代理對象
package com.faith.net.proxy.staticed;

/**
 * 僱員,可以被任何人僱傭。
 * 這個類的作用就是保持被代理類對象的引用,並保證能* 夠調用其方法即可。不需要實現Person類
 */
public class Employee {

    private Person person;

    public Employee(Person person){
        this.person = person;
    }

    public void drive(){
        System.out.println("被僱傭,開始工作:");
        this.person.drive();
        System.out.println("工作結束。");
    }
}
  • 測試類
package com.faith.net.proxy.staticed;

public class StaticProxyTest {

    public static void main(String[] args) {
        Employee employee = new Employee(new Boss());
        employee.drive();
    }
}

靜態代理的缺點,當Person添加新的方法,例如work,被代理類Boss需要實現work方法,並且代理類需要提前知道被代理的引用及其需要被代理的方法。

動態代理可以避免這些麻煩。

動態代理

動態代理中,代理類是運行期自動生成的,無需提前瞭解被代理類的詳細情況。

靜態代理在代理之前,所有東西都是已知的;動態代理在代理之前,所有東西都是未知的。

動態代理最終都會生成一個新的代理類。

jdk動態代理

jdk動態代理中,代理類必須實現InvocationHandler接口,詳細請見代碼註釋。而代理類通過字節碼重組方式實現。

Person及Boss類沿用上例即可。

  • 代理類
package com.faith.net.proxy.jdk;

import com.faith.net.proxy.staticed.Person;

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

/**
 * jdk代理類, 本質上是作爲調用處理器的實現,所以必須要實現調用處理器接口
 */
public class JDKEmployee implements InvocationHandler {

    // 被代理對象的引用
    private Person target;

    public JDKEmployee(Person target) {
        this.target = target;
    }

    // 獲取動態代理對象
    public Object getInstance() throws Exception{
        /**
         *   三個參數:
         *
         *        1、類加載器將加載進入內存中
         *
         *        2、創建出的動態代理對象需要實現哪幾個接口
         *
         *        3、調用處理器,這裏可直接指定爲this,替換掉new JDKEmployee(target)則爲return Proxy.newProxyInstance(JDKEmployee.class.getClassLoader(), new Class[] { Person.class }, new JDKEmployee(target));
         */
        return Proxy.newProxyInstance(JDKEmployee.class.getClassLoader(), new Class[] { Person.class }, new JDKEmployee(target));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         *  InvocationHandler的invoke()方法的參數有三個:
         *
         *     Object proxy:代理對象,也就是Proxy.newProxyInstance()方法返回的對象,通常不用;
         *
         *     Method method:表示當前被調用方法的反射對象;
         *
         *     Object[] args:表示當前被調用方法的參數,沒有參數的args是一個零長數組。
         *
         *   invoke()方法的返回值爲Object類型,它表示當前被調用的方法的返回值
         */
        System.out.println("被僱傭,開始工作:");
        Object invoke = method.invoke(this.target, args);
        System.out.println("工作結束。");
        return invoke;
    }
}
  • 測試類
package com.faith.net.proxy.jdk;

import com.faith.net.proxy.staticed.Person;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;

/**
 * 測試類
 */
public class JDKProxyTest {

    public static void main(String[] args) {

        try {
            Person obj = (Person)new JDKEmployee(new Boss()).getInstance();
            System.out.println(obj.getClass());
            obj.drive();

            //打印出$Proxy0類文件,稍後通過反編譯工具可以查看源代碼
            byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
            FileOutputStream os = new FileOutputStream("D://$Proxy0.class");
            os.write(bytes);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 字節碼重組過程

1、拿到被代理對象的引用,並且獲取到它的所有的接口;

2、JDK Proxy類重新生成一個新的類、同時新的類要實現被代理類所實現的所有接口;

3、動態生成新類的Java代碼,把新加的業務邏輯方法由一定的邏輯代碼去調用;

4、編譯新生成的Java代碼,生成.class字節碼文件

5、將字節碼文件加載到JVM中運行.

這個過程就叫字節碼重組。

  • 分析代理類

上面的

System.out.println(obj.getClass());

會輸出如下結果:

class com.sun.proxy.$Proxy0

按照JDK規範,$開頭的類都是運行時動態生成的,例如內部類。

將$Proxy0類文件輸出並拖入idea中,可以得到反編譯結果如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.faith.net.proxy.staticed.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 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 void drive() 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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.faith.net.proxy.staticed.Person").getMethod("drive");
            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接口:

public final class $Proxy0 extends Proxy implements Person

主要看代理類中的代理方法drive:

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

其中h是父類的,進入其父類Proxy可得:

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

在此場景h就是實現了InvocationHandler接口的代理類JDKEmployee。所以$Proxy0類中drive方法調用的即是JDKEmployee的drive方法。

這就是jdk動態代理的原理。

  • 內部類替代代理類

JDKEmployee類還可以使用內部類來替代,例如:

/**
 * 測試類
 */
public class JDKProxyTest {

    public static void main(String[] args) {

        Person obj = (Person) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class[]{Person.class}, new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //調用方法
                if ("drive".equals(method.getName())) {
                    System.out.println("被僱傭,開始工作:");
                    Object invoke = method.invoke(new Boss(), args);
                    System.out.println("工作結束。");
                    return  invoke;
                } else {
                    return null;
                }
            }
        });
    }
}

因爲JDKEmployee存在的意義就是作爲調用處理器的實現,那麼這個實現當然可以使用內部類來替代。

CGLIB動態代理

jdk動態代理是基於接口實現的,而CGLIB動態代理是通過繼承實現的。

同樣,CGLIB方式需要代理類實現MethodInterceptor接口,其意義也是作爲方法的處理器。示例如下:

  • Boss類
package com.faith.net.proxy.cglib;

/**
 * Boss
 */
public class Boss {

    public void drive(){
        System.out.println("drive..");
    }
}
  • 測試類
package com.faith.net.proxy.cglib;

/**
 * 測試
 */
public class CglibTest {

    public static void main(String[] args) {

        try {
            Boss obj = (Boss)new CglibEmployee().getInstance(Boss.class);
            obj.drive();
            System.out.println(obj.getClass());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章