Java代理模式及其應用

摘要:

  代理根據代理類的產生方式和時機分爲靜態代理和動態代理兩種。代理類不僅可以有效的將具體的實現與調用方進行解耦,通過面向接口進行編碼完全將具體的實現隱藏在內部,而且還可以在符合開閉原則的前提下,對目標類進行進一步的增強。典型地,Spring AOP 是對JDK動態代理的經典應用。


版權聲明:

  本文原創作者:書呆子Rico
  作者博客地址:http://blog.csdn.net/justloveyou_/


一. 代理與代理模式

1). 代理

  代理模式其實很常見,比如買火車票這件小事:黃牛相當於是我們本人的的代理,我們可以通過黃牛買票。通過黃牛買票,我們可以避免與火車站的直接交互,可以省很多事,並且還能享受到黃牛更好的服務(如果錢給夠的話)。在軟件開發中,代理也具有類似的作用,並且一般可以分爲靜態代理和動態代理兩種,上述的這個黃牛買票的例子就是靜態代理。

  那麼,靜態代理與動態代理的區別是什麼呢?所謂靜態代理,其實質是自己手寫(或者用工具生成)代理類,也就是在程序運行前就已經存在的編譯好的代理類。但是,如果我們需要很多的代理,每一個都這麼去創建實屬浪費時間,而且會有大量的重複代碼,此時我們就可以採用動態代理,動態代理可以在程序運行期間根據需要動態的創建代理類及其實例來完成具體的功能。總的來說,根據代理類的創建時機和創建方式的不同,我們可以將代理分爲靜態代理和動態代理兩種形式。

  就像我們也可以自己直接去買票一樣,在軟件開發中,方法直接調用就可以完成功能,爲什麼非要通過代理呢?原因是採用代理模式可以有效的將具體的實現(買票過程)與調用方(買票者)進行解耦,通過面向接口進行編碼完全將具體的實現(買票過程)隱藏在內部(黃牛)。此外,代理類不僅僅是一個隔離客戶端和目標類的中介,我們還可以藉助代理來在增加一些功能,而不需要修改原有代碼,是開閉原則的典型應用。代理類主要負責爲目標類預處理消息、過濾消息、把消息轉發給目標類,以及事後處理消息等。代理類與目標類之間通常會存在關聯關係,一個代理類的對象與一個目標類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用目標類的對象的相關方法,來提供特定的服務。總的來說,

  • 代理對象存在的價值主要用於攔截對真實業務對象的訪問(我們不需要直接與火車站打交道,而是把這個任務委託給黃牛);

  • 代理對象應該具有和目標對象(真實業務對象)相同的方法,即實現共同的接口或繼承於同一個類;

  • 代理對象應該是目標對象的增強,否則我們就沒有必要使用代理了。

事實上,真正的業務功能還是由目標類來實現,代理類只是用於擴展、增強目標類的行爲。例如,在項目開發中我們沒有加入緩衝、日誌這些功能而後期需要加入,我們就可以使用代理來實現,而沒有必要去直接修改已經封裝好的目標類。


2). 代理模式

  代理模式是較常見的模式之一,在許多框架中經常見到,比如Spring的面向切面的編程,MyBatis中緩存機制對PooledConnection的管理等。代理模式使得客戶端在使用目標對象的時候間接通過操作代理對象進行,代理對象是對目標對象的增強,代理模式的UML示意圖如下:

            這裏寫圖片描述

代理模式包含如下幾個角色:

  • 客戶端:客戶端面向接口編程,使用代理角色完成某項功能。

  • 抽象主題:一般實現爲接口,是對(被代理對象的)行爲的抽象。

  • 被代理角色(目標類):直接實現上述接口,是抽象主題的具體實現。

  • 代理角色(代理類):實現上述接口,是對被代理角色的增強。

代理模式的使用本質上是對開閉原則(Open for Extension, Close for Modification)的直接支持。


二. 靜態代理

  靜態代理的實現模式一般是:首先創建一個接口(JDK代理都是面向接口的),然後創建具體實現類來實現這個接口,然後再創建一個代理類同樣實現這個接口,不同之處在於,具體實現類的方法中需要將接口中定義的方法的業務邏輯功能實現,而代理類中的方法只要調用具體類中的對應方法即可,這樣我們在需要使用接口中的某個方法的功能時直接調用代理類的方法即可,將具體的實現類隱藏在底層。下面我們借用CSDN博友frank909在其《輕鬆學,Java 中的代理模式及動態代理》一文中的例子來了解靜態代理:

  我們平常去電影院看電影的時候,在電影開始的階段是不是經常會放廣告呢?

  電影是電影公司委託給影院進行播放的,但是影院可以在播放電影的時候,產生一些自己的經濟收益,比如賣爆米花、可樂等,然後在影片開始結束時播放一些廣告,現在用代碼來進行模擬。


1). 抽象主題(接口)

  首先得有一個接口,通用的接口是代理模式實現的基礎。這個接口我們命名爲Movie,代表電影這個主題。

package com.frank.test;

public interface Movie {
    void play();
}

2). 被代理角色(目標類)與代理角色(代理類)

  然後,我們要有一個真正的實現這個 Movie 接口的類和一個只是實現接口的代理類。

package com.frank.test;
public class RealMovie implements Movie {
    @Override
    public void play() {
        // TODO Auto-generated method stub
        System.out.println("您正在觀看電影 《肖申克的救贖》");
    }
}

  這個表示真正的影片。它實現了 Movie 接口,play()方法調用時,影片就開始播放。那麼代理類呢?

package com.frank.test;

public class Cinema implements Movie {

    RealMovie movie;
    public Cinema(RealMovie movie) {
        super();
        this.movie = movie;
    }

    @Override
    public void play() {
        guanggao(true);    // 代理類的增強處理
        movie.play();     // 代理類把具體業務委託給目標類,並沒有直接實現
        guanggao(false);    // 代理類的增強處理
    }

    public void guanggao(boolean isStart){
        if ( isStart ) {
            System.out.println("電影馬上開始了,爆米花、可樂、口香糖9.8折,快來買啊!");
        } else {
            System.out.println("電影馬上結束了,爆米花、可樂、口香糖9.8折,買回家吃吧!");
        }
    }
}

  Cinema 就是代理對象,它有一個 play() 方法。不過調用 play() 方法時,它進行了一些相關利益的處理,那就是廣告。也就是說,Cinema(代理類) 與 RealMovie(目標類) 都可以播放電影,但是除此之外,Cinema(代理類)還對“播放電影”這個行爲進行進一步增強,即增加了額外的處理,同時不影響RealMovie(目標類)的實現。


3). 客戶端

package com.frank.test;
public class ProxyTest {
    public static void main(String[] args) {
        RealMovie realmovie = new RealMovie();
        Movie movie = new Cinema(realmovie);
        movie.play();
    }
}/** Output
        電影馬上開始了,爆米花、可樂、口香糖9.8折,快來買啊!
        您正在觀看電影 《肖申克的救贖》
        電影馬上結束了,爆米花、可樂、口香糖9.8折,買回家吃吧!
 **/

  現在可以看到,代理模式可以在不修改被代理對象的基礎上(符合開閉原則),通過擴展代理類,進行一些功能的附加與增強。值得注意的是,代理類和被代理類應該共同實現一個接口,或者是共同繼承某個類。如前所述,由於Cinema(代理類)是事先編寫、編譯好的,而不是在程序運行過程中動態生成的,因此這個例子是一個靜態代理的應用。


三. 動態代理

  在第一節我們已經提到,動態代理可以在程序運行期間根據需要動態的創建代理類及其實例來完成具體的功能,下面我們結合具體實例來介紹JDK動態代理。


1). 抽象主題(接口)

  同樣地,首先得有一個接口,通用的接口是代理模式實現的基礎。

package cn.inter;

public interface Subject {
    public void doSomething();
}

2). 被代理角色(目標類)

  然後,我們要有一個真正的實現這個 Subject 接口的類,以便代理。

package cn.impl;

import cn.inter.Subject;

public class RealSubject implements Subject {
    public void doSomething() {
        System.out.println("call doSomething()");
    }
}

3). 代理角色(代理類)與客戶端

  在動態代理中,代理類及其實例是程序自動生成的,因此我們不需要手動去創建代理類。在Java的動態代理機制中,InvocationHandler(Interface)接口和Proxy(Class)類是實現我們動態代理所必須用到的。事實上,Proxy通過使用InvocationHandler對象生成具體的代理代理對象,下面我們看一下對InvocationHandler接口的實現:

package cn.handler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * Title: InvocationHandler 的實現 
 * Description: 每個代理的實例都有一個與之關聯的 InvocationHandler
 * 實現類,如果代理的方法被調用,那麼代理便會通知和轉發給內部的 InvocationHandler 實現類,由它調用invoke()去處理。
 * 
 * @author rico
 * @created 2017年7月3日 下午3:08:55
 */
public class ProxyHandler implements InvocationHandler {

    private Object proxied;   // 被代理對象

    public ProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        // 在轉調具體目標對象之前,可以執行一些功能處理
        System.out.println("前置增強處理: yoyoyo...");

        // 轉調具體目標對象的方法(三要素:實例對象 + 實例方法 + 實例方法的參數)
        Object obj = method.invoke(proxied, args);

        // 在轉調具體目標對象之後,可以執行一些功能處理
        System.out.println("後置增強處理:hahaha...");

        return obj;
    }
}

  在實現了InvocationHandler接口後,我們就可以創建代理對象了。在Java的動態代理機制中,我們使用Proxy類的靜態方法newProxyInstance創建代理對象,如下:

package cn.client;

import java.lang.reflect.Proxy;

import cn.handler.ProxyHandler;
import cn.impl.RealSubject;
import cn.inter.Subject;

public class Test {
    public static void main(String args[]) {

        // 真實對象real
        Subject real = new RealSubject();

        // 生成real的代理對象
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                Subject.class.getClassLoader(), new Class[] { Subject.class },
                new ProxyHandler(real));

        proxySubject.doSomething();
        System.out.println("代理對象的類型 : " + proxySubject.getClass().getName());
        System.out.println("代理對象所在類的父類型 : " + proxySubject.getClass().getGenericSuperclass());
    }
}/** Output
        前置增強處理: yoyoyo...
        call doSomething()
        後置增強處理:hahaha...
        代理對象的類型 : com.sun.proxy.$Proxy0
        代理對象所在類的父類型 : class java.lang.reflect.Proxy
 **/

  到此爲止,我們給出了完整的基於JDK動態代理機制的代理模式的實現。我們從上面的實例中可以看到,代理對象proxySubject的類型爲”com.sun.proxy.$Proxy0”,這恰好印證了proxySubject對象是一個代理對象。除此之外,我們還發現代理對象proxySubject所對應的類繼承自java.lang.reflect.Proxy類,這也正是JDK動態代理機制無法實現對class的動態代理的原因:Java只允許單繼承。


4). JDK中InvocationHandler接口與Proxy類

(1). InvocationHandler接口

  InvocationHandler 是一個接口,官方文檔解釋說:每個代理的實例都有一個與之關聯的 InvocationHandler 實現類,如果代理的方法被調用,那麼代理便會通知和轉發給內部的 InvocationHandler 實現類,由它決定處理。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

  InvocationHandler中的invoke() 方法決定了怎麼樣處理代理傳遞過來的方法調用。


(2). Proxy類

  JDK通過 Proxy 的靜態方法 newProxyInstance 動態地創建代理,該方法在Java中的聲明如下:

    /**     
     * @description 
     * @author rico       
     * @created 2017年7月3日 下午3:16:49     
     * @param loader 類加載器
     * @param interfaces 目標類所實現的接口
     * @param h  InvocationHandler 實例
     * @return     
     */
    public static Object newProxyInstance(ClassLoader loader,
            Class<?>[] interfaces,
            InvocationHandler h)

  事實上,Proxy 動態產生的代理對象調用目標方法時,代理對象會調用 InvocationHandler 實現類,所以 InvocationHandler 是實際執行者。


五. Spring AOP 與 動態代理

  AOP 專門用於處理系統中分佈於各個模塊(不同方法)中的交叉關注點的問題,在 Java EE 應用中,常常通過 AOP 來處理一些具有橫切性質的系統級服務,如事務管理、安全檢查、緩存、對象池管理等,AOP 已經成爲一種非常常用的解決方案。

  AOP機制是 Spring 所提供的核心功能之一,其既是Java動態代理機制的經典應用,也是動態AOP實現的代表。Spring AOP默認使用Java動態代理來創建AOP代理,具體通過以下幾個步驟來完成:

  1. Spring IOC 容器創建Bean(目標類對象);

  2. Bean創建完成後,Bean後處理器(BeanPostProcessor)根據具體的切面邏輯及Bean本身使用Java動態代理技術生成代理對象;

  3. 應用程序使用上述生成的代理對象替代原對象來完成業務邏輯,從而達到增強處理的目的。


引用

輕鬆學,Java 中的代理模式及動態代理
徹底理解JAVA動態代理

發佈了71 篇原創文章 · 獲贊 2099 · 訪問量 121萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章