1. 程式人生 > >通俗易懂講解一下代理模式

通俗易懂講解一下代理模式

1.什麼是代理

In computer programming, the proxy pattern is a software design pattern. A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate.

(所謂的代理者是指一個類別可以作為其它東西的介面。代理者可以作任何東西的介面:網路連線、儲存器中的大物件、檔案或其它昂貴或無法複製的資源。)

​ —— 引用自維基百科

維基百科所說的優點抽象,這裡我們簡單來講就是通過代理控制物件的訪問,可以詳細訪問某個物件的方法,在這個方法呼叫處理,或呼叫後處理。

2.代理的應用場景和分類

①代理應用場景

  • 安全代理 可以遮蔽真實角色
  • 遠端代理 遠端呼叫代理類RMI
  • 延遲載入 先載入輕量級代理類,真正需要在載入真實

②代理的分類

  • 靜態代理(靜態定義代理類)
  • 動態代理(動態生成代理類)
  • Jdk自帶動態代理
  • Cglib 、javaassist(位元組碼操作庫)

3.具體實現步驟

業務場景描述:

​ 某買家去看房,覺得采光和低端都不錯,決定買下這個房子。這個時候買家就找到中介公司,也就是我們的代理者,幫我們的買家處理售前售後工作,並安排好一切交付事項。這裡的一切都圍繞房子所展開,於是我們可以把房源(或者房地產商)當做是我們的介面,然後我們的中介機構代理相關事項,買家談妥後並不需要和房地產商有什麼直接的交涉便可一步到位。

①靜態代理

​ 這裡我們根據房源,分別讓顧客和中介實現相關介面方法,把買家交給中介結構處理相關事項,然後呼叫相關方法即可。

/**
 * 真實房源
 */
interface House {
    void maifang();
}

/**
 * 顧客
 */
class Customer implements House {

    @Override
    public void maifang() {
        System.out.println("我是買家,終於可以買房了");
    }
}

/**
 * 中介
 */
class Proxy implements House {

    // 代理物件
    private Customer customer;

    public Proxy(Customer customer) {
        this.customer = customer;
    }

    @Override
    public void maifang() {
        System.out.println("我是中介,你買房開始交給我啦!");
        customer.maifang();
        System.out.println("我是中介,你買房結束啦...");
    }
}

// 靜態代理
public class Simulation {

    public static void main(String[] args) {

        Customer customer = new Customer();
        House house = new Proxy(customer);
        house.maifang();
    }
}

輸出結果如下:

我是中介,你買房開始交給我啦!
我是買家,終於可以買房了
我是中介,你買房結束啦...
②JDK動態代理

​ 動態代理需要實現InvocationHandler介面,相比靜態代理而言有一個好處就是:不需要生成代理類,可擴充套件性強,方便後續的更改和操作。

/**
 * 真實房源
 */
interface House {
    void maifang();
}

/**
 * 顧客
 */
class Customer implements House {

    @Override
    public void maifang() {
        System.out.println("我是買家,終於可以買房了");
    }

    public void sayHello() {
        System.out.println("sayHello");
    }
}

/**
 * 中介
 * 動態代理
 */

class JdkProxy implements InvocationHandler {

    private Object target;

    public JdkProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("動態代理:我是中介,你買房開始交給我啦!");
        Object invoke = method.invoke(target, args);
        System.out.println("我是中介,你買房結束啦...");
        return invoke;
    }
}


// 動態代理
public class Simulation {

    public static void main(String[] args) {

        Customer customer = new Customer();
        JdkProxy jdkProxy = new JdkProxy(customer);
        House house = (House)Proxy.newProxyInstance(customer.getClass().getClassLoader(),                           customer.getClass().getInterfaces(),jdkProxy);
        house.maifang();
    }
}

輸出結果如下:

動態代理:我是中介,你買房開始交給我啦!
我是買家,終於可以買房了
我是中介,你買房結束啦...
③CGLIB代理

​ CGLIB代理的底層是通過使用一個小而快的位元組碼處理框架ASM,來轉換位元組碼並生成新的類。除了CGLIB包,指令碼語言例如Groovy和BeanShell,也是使用ASM來生成java的位元組碼。當然不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉。

/**
 * 真實房源
 */
interface House {
    void maifang();
}

/**
 * 顧客
 */
class Customer implements House {

    @Override
    public void maifang() {
        System.out.println("我是買家,終於可以買房了");
    }

    public void sayHello() {
        System.out.println("sayHello");
    }
}

/**
 * Cglib代理
 */
class CglibProxy implements MethodInterceptor {

    private Object target;

    public CglibProxy(Object target) {
        this.target = target;
    }

    public Object create() {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(target.getClass().getClassLoader());
        enhancer.setInterfaces(target.getClass().getInterfaces());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy                                   methodProxy) throws Throwable {
        System.out.println("我是中介,你買房開始交給我啦!");
        Object invokeSuper = methodProxy.invoke(target, args);
        System.out.println("我是中介,你買房結束啦...");
        return invokeSuper;
    }
}

public class Simulation {
    public static void main(String[] args) {
        Customer customer = new Customer();
        CglibProxy cglibProxy = new CglibProxy(customer);
        House o = (House) cglibProxy.create();
        o.maifang();
    }
}

​ 輸出結果和上面的效果一樣,這裡不做重複展示,值得一提的CGLIB代理是使用Enhancer來處理相關的類和介面的實現的,這個地方是其獨有的特點。

4.CGLIB代理與JDK動態代理的區別

​ java動態代理是利用反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler來處理。而cglib動態代理是利用asm開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。
①如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP
②如果目標物件實現了介面,可以強制使用CGLIB實現AOP
③如果目標物件沒有實現了介面,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉