由《尋秦記》說代理模式(靜態,動態,CGLib)

卡巴拉的樹發表於2019-03-01

經典穿越劇《尋秦記》被翻拍,看了幾張劇照,不忍直視,週末有空倒是回味了一下古天樂版的,今天看來,依舊經典,童年美好回憶。正好最近在看代理模式,想到如果導演再找古天樂拍戲,倒是不一定能找到古天樂(因為他在玩貪玩藍月??),這時候就可以找到他的經紀人,讓他聯絡古天樂拍戲事宜。這裡面的經紀人就暗含了代理模式的思想。

由《尋秦記》說代理模式(靜態,動態,CGLib)

標準定義: 代理模式為另一個物件提供一個替身或佔位符以控制對這個物件的訪問。

其實Java裡面很多地方應用到了代理,比如Spring AOP,Structs裡面的攔截器。代理模式主要分為靜態代理和動態代理,下面我們舉例子說明。

靜態代理

靜態代理其實就是在程式執行之前,就已經準備好代理類。

假如我們找演員拍《尋秦記》,通過演員的經紀人來商量事宜。

1. 定義介面

首先定義一個演員介面,有一個方法演戲:

public interface Iactor {
    public void act(String dramaName);

複製程式碼

2. 定義實現類

定義演員實現類,假設這裡是古天樂:

public class GuTianle implements Iactor{
    private String name;

    public GuTianle(String name) {
        this.name = name;
    }

    @Override
    public void act(String dramaName) {
        System.out.println(name + " will act " + dramaName);
    }
}
複製程式碼

3.定義代理

代理在這裡也就是古天樂的經紀人:

public class GuTianleProxy implements Iactor {
    private Iactor actor;

    public GuTianleProxy(Iactor actor) {
        this.actor = actor;
    }

    @Override
    public void act(String dramaName) {
        actor.act(dramaName);
    }
}

複製程式碼

經紀人簡單來說就是實現了Iactor介面,內部呼叫真實古天樂的介面,讓他拍戲,這裡為什麼這麼做呢?因為除了讓古天樂拍戲外,在代理類的act方法裡,你也可以新增別的邏輯,比如古天樂太忙了,你就可以幫他推掉這個戲。或者設定一些別的事。

以上一個介面、兩個類就實現了代理模式。

假設這時候導演想要找古天樂拍戲,那麼可以先找他的經紀人:

public class Director {
    public static void main(String args[]){
        Iactor actor = new GuTianle("古天樂");

        Iactor proxy = new GuTianleProxy(actor);

        proxy.act("尋秦記");
    }
}
複製程式碼

執行一下:

由《尋秦記》說代理模式(靜態,動態,CGLib)

其實由靜態代理的例子,我們就可以知道代理模式的大概形式:

由《尋秦記》說代理模式(靜態,動態,CGLib)

首先是Subject,它為RealSubject和Proxy提供了介面,通過實現同一介面,Proxy在RealSubject出現的地方取代它。

RealSubject是真正做事的物件,正如古天樂去拍戲,它是被Proxy代理和控制訪問的物件。

Proxy持有RealSubject的引用,某些時候還會負責RealSubject的建立和銷燬。客戶和RealSubject的互動都必須通過Proxy。所以任何用到RealSubject的地方都可以用Proxy取代,Proxy也控制了對RealSubject的訪問,許多時候我們需要這樣的控制。例如RealSubject是遠端物件,RealSubject建立開銷大,RealSubject需要被保護。

就像明星一般都會配個經紀人,幫助抵擋很多事物,設計模式來源於生活嘛~

下面繼續看動態代理模式。

動態代理

前面講的是動態代理,那麼什麼是動態代理呢?

假設上面的古天樂類有20種方法,我們要是寫經紀人類,那麼也要寫20遍完全一樣的程式碼,去呼叫古天樂類的方法。當然這是很不爽的,Java動態代理在這時候就很有用了,它可以幫助我們在執行時動態生成代理類。與靜態代理相比,介面和實現類都沒有變化,變化的只有代理類。

在使用動態代理時,我們需要定義一個位於代理類和委託類之間的中介類,這個類需要實現InvocationHandler介面:

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

public class GuTianleDynamicProxy implements InvocationHandler{
    private Iactor actor;
    public GuTianleDynamicProxy(Iactor actor)
    {
        this.actor = actor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("I will call Gu Tianle...");
        Object result = method.invoke(actor, args);
        System.out.println("Gu Tianle act so wonderful!!");
        return result;
    }
}
複製程式碼

當我們呼叫代理類物件的方法時,會把呼叫傳到invoke方法中,引數method即呼叫的方法,method.invoke(actor, args)即呼叫actormethod方法,method方法的引數是args。所有呼叫方法都是走的這個invoke函式。

下面看看如何產生動態代理並且呼叫方法的,假設有個動態導演類:

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

public class DynamicDirector {

    public static void main(String args[]){
        Iactor actor = new GuTianle("古天樂");

        InvocationHandler handler = new GuTianleDynamicProxy(actor);

        ClassLoader cl = actor.getClass().getClassLoader();

        Iactor proxy = (Iactor) Proxy.newProxyInstance(cl, actor.getClass().getInterfaces(), handler);

        proxy.act("尋秦記");
    }
}
複製程式碼

上面這段程式碼中,我們依舊建立了實際的演員古天樂,並且用他建立了中介類例項GuTianleDynamicProxy, 再獲取到actorclassLoader,這時候呼叫ProxynewProxyInstance方法,傳入引數生成動態代理類,通過代理執行act方法。

輸出:

由《尋秦記》說代理模式(靜態,動態,CGLib)

這一遍我還在呼叫method前後列印了兩個語句,如果古天樂有多個方法,那麼每個方法呼叫前後都會列印這兩句,是不是想到了Spring的AOP,差不多就是這麼實現的。

總結下基於JDK的動態代理,還是需要定義介面和實現類,然後就是定義一個實現InvocationHandler的中介類,在使用時利用Proxy.newProxyInstance建立動態代理類,最後再呼叫方法。

上面的靜態代理和動態代理都依賴於介面,那麼沒有實現介面的類我們如何做代理呢?答案就是採用CGlib

CGlib動態代理

cglib是針對類來實現代理的,原理是對一個業務類生成一個子類,並且覆蓋其中的業務方法實現代理。

我們繼續舉例,假設古天樂類不需要實現Iactor,直接定義如下:

public class GuTianle {
    public void act(String dramaName) {
        System.out.println("古天樂 is acting " + dramaName);
    }
}
複製程式碼

GClib寫一個GuTianle類的中介類:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGlibProxy implements MethodInterceptor{
    private Object actor; //實際的演員,供代理方法中進行真正的方法呼叫

    public Object getInstance(Object actor) {
        this.actor = actor;
        Enhancer enhancer = new Enhancer();//建立加強器,用來建立動態代理類
        enhancer.setSuperclass(this.actor.getClass());//為加強器指定要代理的業務類(即:為下面生成的代理類指定父類)
        //設定回撥:對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實現intercept()方法進行攔
        enhancer.setCallback(this);
        // 建立動態代理類物件並返回  
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before call...");
        methodProxy.invokeSuper(o, objects);
        System.out.println("after call...");
        return null;
    }
}

複製程式碼

最後使用時動態建立代理類,動態代理類是要代理的演員類的子類:

public class CGlibDirector {
    public static void main(String[] args) {
        GuTianle actor = new GuTianle();
        CGlibProxy cglib = new CGlibProxy();
        GuTianle cglib_proxy = (GuTianle)cglib.getInstance(actor);
        cglib_proxy.act("尋秦記");
    }
}
複製程式碼

可以得到:

由《尋秦記》說代理模式(靜態,動態,CGLib)

下一篇深入分析下動態代理和CGlib底層的實現原理,歡迎點贊~

相關文章