1. 程式人生 > 其它 >Java代理

Java代理

java代理

一、代理模式

代理模式給某一個物件提供一個代理物件,並由代理物件控制對原物件的引用。同時代理物件可以呼叫被代理物件的方法,並對其進行增強。可以總結為代理物件 = 增強程式碼 + 目標物件(原物件)

比如張三去買房子,大致的步驟是找房子,商量價錢,然後交錢辦手續,但是張三每天要上班沒有那麼多時間和精力,所以他找了一箇中介,代理他去辦這件事,這個中介不僅做了上面的那些事,而且還會完成其他的事。

二、Java靜態代理

靜態代理的步驟:

  • 定義介面
    • 代理和被代理類都需要實現此介面
  • 定義被代理類
    • 被代理類需要實現介面
  • 定義代理類
    • 代理類需要實現介面
    • 建立被代理物件,呼叫方法時呼叫被代理物件的方法,同時可以對方法進行增加增強

實現上面的例子:

public interface BuyHouse {
    //找房子
    void findHouse();
    //商議價格
    void discussPrice();
    //交錢辦房產證
    void payAndHaveHousePropertyCard();
}
public class ZhangSan implements BuyHouse{
    @Override
    public void findHouse() {
        System.out.println("找房子");
    }

    @Override
    public void discussPrice() {
        System.out.println("商議價格");
    }

    @Override
    public void payAndHaveHousePropertyCard() {
        System.out.println("交錢辦房產證");
    }
}
public class IntermediaryProxy implements BuyHouse{
    public IntermediaryProxy(ZhangSan zhangSan) {
        this.zhangSan = zhangSan;
    }

    private ZhangSan zhangSan;

    @Override
    public void findHouse() {
        System.out.println("中介公司聽取了張三的對房子的需求");
        zhangSan.findHouse();
        System.out.println("中介公司開始找房子");
    }

    @Override
    public void discussPrice() {
        zhangSan.discussPrice();
        System.out.println("中介公司協調價錢");
    }

    @Override
    public void payAndHaveHousePropertyCard() {
        zhangSan.payAndHaveHousePropertyCard();
        System.out.println("中介公司幫忙處理相關手續");
    }
}

測試類

public class Test {
    public static void main(String[] args) {
        ZhangSan zhangSan = new ZhangSan();
        IntermediaryProxy proxy = new IntermediaryProxy(zhangSan);
        proxy.findHouse();
        proxy.discussPrice();
        proxy.payAndHaveHousePropertyCard();
    }
}

執行結果:

中介公司聽取了張三的對房子的需求 找房子 中介公司開始找房子 商議價格 中介公司協調價錢 交錢辦房產證 中介公司幫忙處理相關手續

如果一個類需要被代理,就得去建立一個代理類。如果被代理的類過多,這樣就需要手動建立很多代理類。為了解決這個問題,便有了動態代理

三、Java動態代理

在瞭解動態代理之前需要了解什麼是類載入和反射,這裡是我的部落格地址Java類載入與反射

利用反射實現動態代理,繼續實現上述例子。

在繼續瞭解前首先複習一下物件的建立過程,如下圖:(Loserfromlazy是我的水印)

由上圖可見,建立一個物件最主要的是得到對應的Class物件。Class物件包含了一個類的所有資訊,而代理類和被代理類(也就是介面實現類)實現類同一個介面,介面擁有代理物件和目標物件共同的類資訊。所以,我們可以從介面那得到理應由代理類提供的資訊。

JDK提供了java.lang.reflect.InvocationHandler介面和 java.lang.reflect.Proxy類,這兩個類相互配合,完成jdk的動態代理。

Proxy有個靜態方法:getProxyClass(ClassLoader, interfaces),只要你給它傳入類載入器和一組介面,它就給你返回代理Class物件。根據代理Class的構造器建立物件時,需要傳入InvocationHandler。每次呼叫代理物件的方法,最終都會呼叫InvocationHandlerinvoke()方法。

以下是原始碼中關於Proxy的例子:

To create a proxy for some interface Foo:
       InvocationHandler handler = new MyInvocationHandler(...);
       Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
       Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
                       newInstance(handler);
   
or more simply:
       Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                            new Class<?>[] { Foo.class },
                                            handler);

由原始碼給的示例Proxy類主要有兩個靜態方法

  • getProxyClass

    • 這個方法, 會從你傳入的介面Class中,“拷貝”類結構資訊到一個新的Class物件中,但新的Class物件帶有構造器,是可以建立物件的
  • newProxyInstance

    (一般直接用這個)

    • 封裝了得到代理類Class物件、建構函式等細節,直接返回了代理物件

下面進行動態代理的改變,介面和被代理類不變

public interface BuyHouse {
    //找房子
    void findHouse();
    //商議價格
    void discussPrice();
    //交錢辦房產證
    void payAndHaveHousePropertyCard();
}
public class ZhangSan implements BuyHouse{
    @Override
    public void findHouse() {
        System.out.println("找房子");
    }

    @Override
    public void discussPrice() {
        System.out.println("商議價格");
    }

    @Override
    public void payAndHaveHousePropertyCard() {
        System.out.println("交錢辦房產證");
    }
}

通過反射獲得代理物件

public class Test2 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //獲取代理類的Class物件
        Class<?> proxyClass = Proxy.getProxyClass(BuyHouse.class.getClassLoader(), BuyHouse.class);
        //獲得建構函式
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
        //建立代理物件
        BuyHouse proxy = (BuyHouse) constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //建立被代理物件,用於呼叫方法
                ZhangSan zhangSan = new ZhangSan();
                //通過invoke方法呼叫被代理物件的方法
                Object invoke = method.invoke(zhangSan, args);
                //增強
                System.out.println("中介進行處理");
                return invoke;
            }
        });

        proxy.findHouse();
        proxy.discussPrice();
        proxy.payAndHaveHousePropertyCard();
        System.out.println("代理結束");

    }
}

執行結果

找房子 中介進行處理 商議價格 中介進行處理 交錢辦房產證 中介進行處理 代理結束

我們可以對上面的方法進行封裝

public class Test2 {
    // 該方法返回一個代理物件
    public static Object getProxy(Object target) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // target為被代理物件,得到其代理類的Class物件
        Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());

        // 獲得建構函式
        Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);

        // 獲得代理物件
        Object targetProxy = constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invoke = method.invoke(target, args);
                System.out.println("代理類增強方法");
                return invoke;
            }
        });
        return targetProxy;
    }
    
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        ZhangSan zhangSan = new ZhangSan();
        BuyHouse proxy = (BuyHouse) getProxy(zhangSan);
        proxy.findHouse();
        proxy.discussPrice();
        proxy.payAndHaveHousePropertyCard();
        System.out.println("代理結束");

    }
}

通過newProxyInstance()獲得代理物件

上面的封裝方法是我們自己寫的,其實通過Proxy類的**newProxyInstance()**可以直接返回代理物件

    public static Object getProxyByProxyMethod(Object target) {
        // 直接返回代理物件
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invoke = method.invoke(target, args);
                System.out.println("代理類增強方法");
                return invoke;
            }
        });
    }

我們自己獲取代理物件的步驟是

  • 通過getProxyClass獲得代理物件的Class物件(介面+對應的建構函式)
  • 通過Class物件呼叫得到構造方法
  • 構造方法去建立例項
    • 構造方法傳入InvocationHandler例項,需要實現其invoke方法

newProxyInstance幫我們獲取代理的步驟和上面類似,只不過Class物件是直接通過**getProxyClass0(loader, intfs)**來獲取的。而我們自己封裝的程式碼中,使用的是getProxyClass方法。但是該方法最終還是呼叫的getProxyClass0(loader, intfs)來獲取的Class物件

四、基於CGLib的動態代理

  • CGLIB(Code Generator Library)是一個強大的、高效能的程式碼生成庫。其被廣泛應用於AOP框架(Spring)中,用以提供方法攔截操作。
  • CGLIB代理主要通過對位元組碼的操作,以控制物件的訪問。CGLIB底層使用了ASM(一個短小精悍的位元組碼操作框架)來操作位元組碼生成新的類。
  • CGLIB相比於JDK動態代理更加強大
    • JDK動態代理雖然簡單易用,但只能對介面進行代理。
    • 如果要代理的類為一個普通類,沒有介面,那麼Java動態代理就沒法使用了。
  • Java動態代理使用Java原生的反射API進行操作(執行期),在生成類上比較高效。
  • CGLIB使用ASM框架直接對位元組碼進行操作(編譯期),在類的執行過程中比較高效

4.1 CGLib介紹

4.1.1 EnHancer

  • Enhancer既能夠代理普通的class,也能夠代理介面。
  • Enhancer建立一個被代理物件的子類並且攔截所有的方法呼叫(包括從Object中繼承的toString和hashCode方法)。
  • Enhancer不能夠攔截final類與方法。

常用方法:

//    用來設定父型別
Enhancer.setSuperclass(Class superclass);

//    增強
Enhancer.setCallback(Callback callback);
Enhancer.setCallback(new InvocationHandler(){});
Enhancer.setCallback(new MethodInterceptor(){});
//    方法是用來建立代理物件,其提供了很多不同引數的方法用來匹配被增強類的不同構造方法。
Enhancer.create(Class type, Callback callback);
Enhancer.create(Class superclass, Class[] interfaces, Callback callback);
Enhancer.create(Class[] argumentTypes, Object[] arguments);

4.1.2 Callback

Callback是一個空的介面,在Cglib中它的實現類有以下幾種:

最常用的就是第一種

  1. MethodInterceptor
  2. NoOp
  3. LazyLoader
  4. Dispatcher
  5. InvocationHandler
  6. FixedValue

MethodInterceptor

它可以實現類似於AOP程式設計中的環繞增強(around-advice)。

    它只有一個方法:
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy)

    代理類的所有方法呼叫都會轉而執行這個介面中的intercept方法而不是原方法。
    如果需要在intercept方法中執行原方法可以使用引數method進行反射呼叫,
    或者使用引數proxy 一 proxy.invokeSuper(obj, args);
    後者會快一些(反射呼叫比正常的方法呼叫的速度慢很多)。
    MethodInterceptor允許我們完全控制被攔截的方法,並且提供了手段對原方法進行呼叫,

    因為 MethodInterceptor的效率不高,它需要產生不同型別的位元組碼,
    並且需要生成一些執行時物件(InvocationHandler就不需要),所以Cglib提供了其它的介面供我們選擇。

InvocationHandler

它的使用方式和MethodInterceptor差不多。 需要注意的一點是,所有對invoke()方法的引數proxy物件的方法呼叫都會被委託給同一個InvocationHandler,所以可能會導致無限迴圈。

NoOp

這個介面只是簡單地把方法呼叫委託給了被代理類的原方法,不做任何其它的操作

LazyLoader

它也提供了一個方法:Object loadObject() loadObject()方法會在第一次被代理類的方法呼叫時觸發,它返回一個代理類的物件,這個物件會被儲存起來然後負責所有被代理類方法的呼叫,一種lazy模式。如果被代理類或者代理類的物件的建立比較麻煩,而且不確定它是否會被使用,那麼可以選擇使用這種lazy模式來延遲生成代理。

Dispatcher

Dispatcher和LazyLoader介面相同,也是提供了loadObject()方法。不過它們之間不同的地方在於,Dispatcher的loadObject()方法在每次發生對原方法的呼叫時都會被呼叫並返回一個代理物件來呼叫原方法。也就是說Dispatcher的loadObject()方法返回的物件並不會被儲存起來,可以類比成Spring中的Prototype型別,而LazyLoader則是lazy模式的Singleton。

4.1.3 FastClass

FastClass不使用反射類(Constructor或Method)來呼叫委託類方法,而是動態生成一個新的類(繼承FastClass),向類中寫入委託類例項直接呼叫方法的語句,用模板方式解決Java語法不支援問題,同時改善Java反射效能。

動態類為委託類方法呼叫語句建立索引,使用者根據方法簽名(方法名+引數型別)得到索引值,再通過索引值進入相應的方法呼叫語句,得到呼叫結果。

4.2 例項:使用CGLib的EnHancer實現動態代理

被代理的類:

public class Lisi{
    public void findHouse() {
        System.out.println("找房子");
    }

    public void discussPrice() {
        System.out.println("商議價格");
    }

    public void payAndHaveHousePropertyCard() {
        System.out.println("交錢辦房產證");
    }
}

自定義攔截器實現MethodInterceptor:

public class TestInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前置增強
        System.out.println("中介瞭解詳情");

        // 被代理類執行的方法(被增強的方法)
        // 注意這裡是呼叫invokeSuper而不是invoke,否則死迴圈;
        // methodProxy.invokeSuper執行的是原始類的方法;
        // method.invoke執行的是子類的方法;
        Object invoke = methodProxy.invokeSuper(o, objects);

        // 後置增強
        System.out.println("中介進行處理");

        return invoke;
    }
}

使用Enhancer建立代理物件:

public class Test3 {
    public static void main(String[] args) {
        //建立增強類
        Enhancer enhancer = new Enhancer();
        //傳入被代理的類
        enhancer.setSuperclass(Lisi.class);
        //設定回撥函式,傳入自定義的攔截器
        enhancer.setCallback(new TestInterceptor());
        //獲取代理物件
        Lisi proxy = (Lisi) enhancer.create();

        proxy.findHouse();
        proxy.discussPrice();
        proxy.payAndHaveHousePropertyCard();
        System.out.println("結束代理");
    }
}

結果:

中介瞭解詳情 找房子 中介進行處理 中介瞭解詳情 商議價格 中介進行處理 中介瞭解詳情 交錢辦房產證 中介進行處理 結束代理

作者:Loserfromlazy 出處:https://home.cnblogs.com/u/yhr520/ 本文版權歸作者和部落格園共有,歡迎轉載,但必須給出原文連結,並保留此段宣告,否則保留追究法律責任的權利。