java設計模式之代理模式

在有些情況下,客戶不能或者不想直接訪問另一個對象,這時需要找一箇中介幫忙來完成某項任務,這個中介就是代理對象.比如租房子,不一定直接去找現房,可以找中介幫忙,找工作可以通過獵頭等等.

代理模式的定義與特點

代理模式的定義:由於某些原因需要給某對象提供一個代理以控制對該對象的訪問,這時訪問對象不適合或者不能直接引用目標對象,代理對象作爲訪問對象和目標對象之間的中介.

代理模式的主要優點有:

  • 代理模式在客戶端與目標對象之間起到一箇中介作用和保護目標對象的作用.
  • 代理對象可以拓展目標對象的功能
  • 代理模式能將客戶端與目標對象分離,在一定程序上降低了系統的耦合度.

其主要缺點是:

  • 在客戶端和目標對象之間增加了一個代理對象,會造成請求處理速度變慢
  • 增加了系統的複雜度

靜態代理

靜態代理模式的結構

代理模式的結構比較簡單,主要是通過定義一個繼承抽象主題的代理來包含真實主題,從而實現對真實主題的訪問.

代理模式的主要角色如下:

  1. 抽象主題(subject)角色:通過接口或抽象類聲明真實主題和代理對象實現的業務方法.
  2. 真實主題(real subject)角色:也叫被委託角色,被代理角色,實現了抽象主題中的具體業務,是代理對象所代表的真實對象,是最終要引用的對象.
  3. 代理(proxy)角色:提供了與真實主題相同的接口,其內部含有對真實主題的引用,它可以訪問、控制、擴展真實主題的功能,並在真實主題角色處理完畢前後做預處理和善後處理工作.

其結構圖如下:
代理模式

靜態代理模式的實現

/**
 * 抽象主題
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 10:07
 * @since 1.0.0+
 */
public interface Subject {
    /**
     * 待具體實現的方法
     */
    void request();
}

/**
 * 真實主題對象
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 10:08
 * @since 1.0.0+
 */
public class RealSubject implements Subject {

    /**
     * 待具體實現的方法
     */
    @Override
    public void request() {
        System.out.println("訪問真實主題方法");
    }
}

/**
 * 代理對象
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 10:09
 * @since 1.0.0+
 */
public class Proxy implements Subject {

    private RealSubject realSubject;

    /**
     * 待具體實現的方法
     */
    @Override
    public void request() {
        if(realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.request();
        postRequest();
    }

    private void postRequest() {
        System.out.println("訪問真實主題以後的後續處理");
    }

    private void preRequest() {
        System.out.println("訪問真實主題之前的預處理");
    }

}


/**
 * 靜態代理的客戶端調用
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 10:12
 * @since 1.0.0+
 */
public class ProxyClient {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.request();
    }
}

代理模式擴展

普通代理

普通代理要求客戶端自能訪問代理角色,不能訪問真實角色.也就是不能直接new RealSubject對象,必須由
proxy角色來進行.普通代理比較常見,上述的靜態代理實現方式其實就是普通代理.

強制代理

我們一般的思維是通過代理模式找到真實的角色,但是強制代理反其道行之,必須強制通過真實角色去查找到其代理角色.
總而言之強制代理就是通過new 或者其他方式創建一個真實角色對象,真實角色對象會返回它的代理對象,然後通過代理
纔可能進行一系列的操作.現實中最常見的例子就是某個明星和經紀人.好吧,你如果知道明星的聯繫方式,找到了他,結果他告訴你,
你去找我的經紀人XX吧,他會負責處理的.

具體實現如下:

/**
 * 抽象主題
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 10:07
 * @since 1.0.0+
 */
public interface Subject {
    /**
     * 待具體實現的方法
     */
    void request();

    /**
     * 獲取每個具體實現對應的代理對象實例
     * @return 返回對應的代理對象
     */
    Subject getProxy();
}

/**
 * 強制代理對象
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 14:36
 * @since 1.0.0+
 */
public class ForceProxy implements Subject {

    private Subject subject = null;

    public ForceProxy(Subject subject) {
        this.subject = subject;
    }

    /**
     * 待具體實現的方法
     */
    @Override
    public void request() {
        preRequest();
        this.subject.request();
        postRequest();
    }

    /**
     * @return 返回對應的代理對象就是自己
     */
    @Override
    public Subject getProxy() {
        return this;
    }

    private void postRequest() {
        System.out.println("訪問真實主題以後的後續處理");
    }

    private void preRequest() {
        System.out.println("訪問真實主題之前的預處理");
    }
}

/**
 * 具體的實現對象
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 14:35
 * @since 1.0.0+
 */
public class RealSubject implements Subject {

    /**
     * 該具體實現對象的代理對象
     */
    private Subject proxy = null;

    @Override
    public Subject getProxy(){
        this.proxy = new ForceProxy(this);
        return this.proxy;
    }

    /**
     * 待具體實現的方法
     */
    @Override
    public void request() {
        System.out.println("訪問真實主題方法");
    }
}


/**
 * 強制代理客戶端調用
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 14:40
 * @since 1.0.0+
 */
public class ForceProxyClient {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject proxy = subject.getProxy();
        proxy.request();
    }
}

動態代理

什麼是動態代理?動態代理是在實現階段不用關心代理誰,而在運行階段由JVM指定代理哪一個對象.相對來說,自己寫代理類的方式就是靜態代理,動態代理中,代理類並不是在java代碼中定義的,而是在運行時根據我們在java代碼中的指示動態生成.相對於靜態代理,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法.

有一個名詞叫做面向切面編程,也就是AOP(Aspect Oriented Programming),其核心就是採用了動態代理機制.

JDK動態代理

因爲動態代理的靈活特性,我們在設計動態代理類(dynamicProxy)時,不用顯示的讓它的實現與真實主題類(realSubject)
相同的接口,而是把這個實現推遲到運行時.

爲了能讓DynamicProxy類能夠在運行時纔去實現RealSubject類已實現的一系列接口並執行接口中的相關方法操作,
需要讓dynamicProxy類實現JDK自帶的java.lang.reflect.InvocationHandler接口,該接口中的invoke()方法能夠
讓DynamicProxy實例在運行時調用被代理類需要對外實現的所有接口中的方法,也就是完成對真實主題類方法的調用.

所以我們必須先把真實主題類(realSubject)中已實現的所有接口都加載到JVM中,然後我們就可以創建一個
真實主題類的實例,獲取該實例的類加載器ClassLoader(所謂的類加載器,即使具有某個類的定義,內部相關結構,包括繼承樹,方法區等等).

java動態代理機制中有兩個重要的類和接口InvocationHandler(接口)和Proxy(類),這一個類Proxy和接口InvocationHandler是我們實現動態代理的核心

因此JDK動態代理的步驟如下:

  1. 定義一個事件管理器,實現InvocationHandler接口,並重寫invoke(被代理類,被代理的方法,方法的參數列表)方法
  2. 實現具體主題對象類(realSubject)
  3. 調用Proxy.newProxyInstance(真實主題類加載器,真實主題類實現的接口,事件管理器對象)生成一個代理實例.
  4. 通過該代理實例調用方法

具體實現如下:

/**
 * 抽象主題
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 10:07
 * @since 1.0.0+
 */
public interface Subject {
    /**
     * 待具體實現的方法
     */
    void request();
}

/**
 * 真實主題類
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 11:13
 * @since 1.0.0+
 */
public class RealSubject implements Subject {
    /**
     * 具體實現的方法
     */
    @Override
    public void request() {
        System.out.println("訪問真實主題方法");
    }
}

/**
 * 事件管理器,代理類的調用程序
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 11:14
 * @since 1.0.0+
 */
public class DynamicProxy implements InvocationHandler {

    /**
     * 代理類中的真實對象
     */
    private Object target;

    /**
     * 構造函數
     * @param target 真實對象
     */
    public DynamicProxy(Object target) {
        super();
        this.target = target;
    }

    /**
     *
     * @param proxy 被代理的類(真實代理對象)
     * @param method 被代理的類的方法
     * @param args 方法的參數列表
     * @return 代理對象方法的返回結果或者真實代理對象(com.sun.proxy.$Proxy0)
     * @throws Throwable Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before 動態代理");
        method.invoke(target, args);
        System.out.println("after 動態代理");
        return null;
    }
}

/**
 * JDK動態代理調用
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 11:59
 * @since 1.0.0+
 */
public class DynamicTest {

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        InvocationHandler dynamicProxy = new DynamicProxy(realSubject);
        Class<?> cls = realSubject.getClass();

        Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), dynamicProxy);
        subject.request();
    }
}

cglib動態代理

AOP的源碼中用到了兩種動態代理來實現攔截切入的功能:JDK動態代理和cglib動態代理
JDK的動態代理機制只能代理實現了接口的類,否則不能實現JDK的動態代理.因此JDK動態代理有一定的侷限性,cglib是針對類來實現代理的,
它的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,因爲採用的是繼承,所以不能對final修飾符的類進行代理.

因爲JAVA只允許單繼承,而JDK生成的代理類本身就繼承了Proxy類,因此使用cglib來實現繼承式的動態代理

CGLIB(Code Generation Library)是一個開源項目,是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口,通俗說cglib可以在運行時動態生成字節碼。

具體實現如下:

/**
 * 具體主題對象,注意沒有實現任何接口
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 12:16
 * @since 1.0.0+
 */
public class RealSubject {

    public void request() {
        System.out.println("訪問真實主題方法");
    }
}


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

import java.lang.reflect.Method;

/**
 * 動態代理類
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 12:17
 * @since 1.0.0+
 */
public class CglibProxy implements MethodInterceptor {
    /**
     * 通過Enhancer 創建代理對象
     */
    private Enhancer enhancer = new Enhancer();

    /**
     * 通過class對象獲取代理對象
     * @param clazz class對象
     * @return 代理對象
     */
    public Object getProxy(Class<?> clazz) {
        //設置enhancer對象的父類
        enhancer.setSuperclass(clazz);
        //設置enhancer的回調
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     *
     * @param object 被代理對象
     * @param method 被代理對象的方法
     * @param args 被代理對象的方法參數
     * @param methodProxy 代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before 動態代理");
        //調用新生成的cglib的代理對象,所屬的父類的被代理方法
        Object result = methodProxy.invokeSuper(object, args);
        System.out.println("after 動態代理");
        return result;
    }
}

/**
 * cglib動態代理的調用
 *
 * @author Jonathan
 * @version 1.0.0
 * @date 2019/8/29 12:44
 * @since 1.0.0+
 */
public class CglibTest {

    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        RealSubject realSubject = (RealSubject) proxy.getProxy(RealSubject.class);
        realSubject.request();
    }
}

代理模式的擴展

大到一個系統框架、企業平臺,小到代碼片段、事務處理,很多地方都會用到代理模式,該模式應該是我們接觸最多的模式,而且有了AOP我們寫代理就更加簡單了,有類似於Spring AOP 和AspectJ這樣的優秀工具,拿來就可以使用.

在學習AOP框架時,我們只需要弄清楚幾個名詞即可:切面(Aspect)、切入點(pointCut)、通知(advice)、目標(target)、 代理(proxy) 、連接點(joinPoint)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章