【設計模式】代理模式(動態代理)

前言:本篇文章爲閱讀《Head First設計模式》一書中的代理模式一章後整理而來,本篇博文主要介紹該章節提到的動態代理(保護代理),後續會補上該章節中講到的遠程代理和虛擬代理。


一、使用Java API的代理,創建一個保護代理

Java在java.lang.reflect包中有自己的代理支持,通過這個包可以在運行時動態的創建代理類,實現一個或多個接口,並將方法的調用轉發到所指定的類。因爲實際的代理類是在運行時創建的,因此稱這種Java技術爲:動態代理。

接下來要利用Java的動態代理創建一個代理實現(保護代理)。在此之前,先來看一下類圖,瞭解一下動態代理是怎麼一回事。

這裏寫圖片描述

Java爲我們創建了Proxy類,但需要告訴Proxy類我們要做什麼。但是我們不能將代碼放入Proxy類中,因爲Proxy不是我們直接創建的。所以這時需要將代碼放入InvocationHandler中。InvocationHandler的工作是響應代理的任何調用,可以把InvocationHandler當成代理收到方法調用後,請求做實際工作的對象。


二、實現動態代理

假設我們需要實現一個約會服務系統。該服務系統中涉及到一個接口PersonBean,通過該接口允許設置或獲取一個人的信息。
PersonBean接口代碼如下:

package com.pattern.proxy.dynamic;

/**
 * PersonBean接口,通過該接口允許設置或取得一個人的信息
 * 
 * @date:2017年3月13日 下午9:54:02    
 */
public interface PersonBean {

    // 獲取姓名
    String getName();

    // 獲取性別
    String getGender();

    // 獲取興趣愛好
    String getInterests();

    // HotOrNot評分
    int getHotOrNotRating();

    void setName(String name);

    void setGender(String gender);

    void setInterests(String interests);

    void setHotOrNotRating(int rating);

}

PersonBeanImpl實現PersonBean接口,代碼如下:

package com.pattern.proxy.dynamic;

/**
 * 實現PersonBean接口
 * 
 * @date:2017年3月13日 下午10:04:12    
 */
public class PersonBeanImpl implements PersonBean {

    private String name;

    private String gender;

    private String interests;

    private int rating;

    private int ratingCount = 0;

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getGender() {
        return gender;
    }

    @Override
    public String getInterests() {
        return interests;
    }

    @Override
    public int getHotOrNotRating() {
        if(ratingCount == 0)
            return 0;
        return rating / ratingCount;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public void setInterests(String interests) {
        this.interests = interests;
    }

    @Override
    public void setHotOrNotRating(int rating) {
        this.rating += rating;
        ratingCount++;
    }

}

現在有如下需求,該系統允許用戶設置自己的信息,但是不應該允許用戶篡改別人的數據。但是HotOrNot評分則相反,用戶不能更改自己的評分,但是可以給他人評分。在目前PersonBean實現中所有的方法都是公開的,任何人都可以調用。

所以現在我們需要解決這些問題,要修正這些問題,必須創建兩個代理:一個代理訪問自己的PersonBean對象,另一個訪問其他用戶的PersonBean對象。創建這種代理,我們必須使用Java API的動態代理。Java會爲我們創建兩個代理,我們只需要提供handler來處理代理轉發來的方法。我們需要寫兩個InvocationHandler,其中一個給擁有者使用,另一個給非擁有者使用。當代理的方法被調用時,代理就會把這個調用轉發給InvocationHandler,但這個並不是通過調用InvocationHandler的相應方法做到的。那是如何做到的?讓我們看一下InvocationHandler接口:

這裏寫圖片描述
InvocatonHandler只有一個invoke()的方法,不管代理被調用的是何種方法,處理器被調用的一定是invoke()方法。工作過程如下:

1.假設proxy的setHotOrNotRating()方法被調用。

proxy.setHotOrNotRating(9);

2.proxy會接着調用InvocationHandler的invoke()方法。

invoke(Object proxy, Method method, Object[] args)

3.handler決定如何處理請求。

return method.invoke(person, args);

實現OwnerInvocationHandler,代碼如下:

package com.pattern.proxy.dynamic;

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

/**
 * 調用處理器實現了InvocationHandler接口
 * 當用戶需要查看或設置自己的信息時,調用該處理器進行訪問控制
 * 
 * @date:2017年3月13日 下午10:17:58    
 */
public class OwnerInvocationHandler implements InvocationHandler {

    private PersonBean personBean;

    // OwnerInvocationHandler持有PersonBean類對象的引用
    public OwnerInvocationHandler(PersonBean personBean) {
        this.personBean = personBean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {

        try {
            if(method.getName().startsWith("get")) { // 用戶允許查看自己的相關信息
                return method.invoke(personBean, args);
            } else if(method.getName().equals("setHotOrNotRating")) { // 用戶不能自己給自己打分
                throw new IllegalAccessException();
            } else if(method.getName().startsWith("set")) { // 用戶可以對其他信息進行設置
                return method.invoke(personBean, args);
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        // 如果調用的是其他的方法,一律處理,返回null
        return null;
    }

}

實現NonOwnerInvocationHandler,代碼如下:

package com.pattern.proxy.dynamic;

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

/**
 * 用戶訪問其他用戶時通過該處理類進行訪問處理
 * 
 * @date:2017年3月13日 下午10:37:45    
 */
public class NonOwnerInvocationHandler implements InvocationHandler {

    private PersonBean personBean;

    public NonOwnerInvocationHandler(PersonBean personBean) {
        this.personBean = personBean;
    }

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

        if(method.getName().startsWith("get")) { // 允許訪問獲取其他用戶的信息
            return method.invoke(personBean, args);
        } else if(method.getName().equals("setHotOrNotRating")) { // 可以給其他用戶進行打分
            return method.invoke(personBean, args);
        } else if(method.getName().startsWith("set")) { // 不能更改其他用戶的信息
            throw new IllegalAccessException();
        }

        // 對其他的方法調用不處理,放回null
        return null;
    }

}

接下來,我們需要創建動態Proxy類,並實例化Proxy對象。我們創建擁有者代理,該代理可以將它的方法調用轉發給OwnerInvocationHandler,代碼如下:

/**
     * 獲取用戶處理自己信息的代理對象
     * 
     * 此方法需要一個PersonBean對象作爲參數,然後返回
     * 他的代理,因爲代理和被代理對象(主題subject)
     * 實現了相同的接口,該方法最終返回一個PersonBean
     * 
     * @param personBean
     * @return
     */
    public PersonBean getOwnerProxy(PersonBean personBean) {
        return (PersonBean) Proxy.newProxyInstance( // 通過Proxy類的靜態方法newProxyInstance創建代理
                personBean.getClass().getClassLoader(),  // 將PersonBean的類載入器當作參數
                personBean.getClass().getInterfaces(),  // 代理需要實現的接口
                new OwnerInvocationHandler(personBean)); // 處理器
    }

非擁有者的代理類創建代碼如下:

/**
     * 獲取用戶處理他人信息的代理對象
     * 
     * @param personBean
     * @return
     */
    public PersonBean getNonOwnerProxy(PersonBean personBean) {
        return (PersonBean) Proxy.newProxyInstance(
                personBean.getClass().getClassLoader(), 
                personBean.getClass().getInterfaces(), 
                new NonOwnerInvocationHandler(personBean));
    }

最後我們來測試下,測試代碼如下:

package com.pattern.proxy.dynamic;

import java.lang.reflect.Proxy;

public class ProxyTestDriver {

    private PersonBean personBean;

    public ProxyTestDriver() {

        /* 
         * 初始化數據庫
         * initializeDatabase();
         * */

        personBean = new PersonBeanImpl();
        personBean.setName("Joe Javabean");
        personBean.setInterests("Singing");
        personBean.setGender("male");
        personBean.setHotOrNotRating(7);
    }

    public static void main(String[] args) {
        ProxyTestDriver testDriver = new ProxyTestDriver();
        testDriver.driver();
    }

    public void driver() {
        /*
         * 從數據庫中讀取一個人信息
         * PersonBean personBean = getPersonFromDataBase("Joe Javabean");
         */
        PersonBean joe = personBean;
        // 創建處理自己信息的代理對象
        PersonBean ownerProxy = getOwnerProxy(joe);
        System.out.println("Name is " + ownerProxy.getName());
        System.out.println("before set interests:" + ownerProxy.getInterests());
        // 用戶修改了自己的愛好
        ownerProxy.setInterests("reading");
        System.out.println("after set interests:" + ownerProxy.getInterests());
        try { // 用戶嘗試修改自己的評分
            ownerProxy.setHotOrNotRating(9);
        } catch (Exception e) {
            System.out.println("can't set rating from owner proxy");
        }
        System.out.println("rating is " + ownerProxy.getHotOrNotRating());

        System.out.println("========================================");

        // 創建處理他人信息的代理類
        PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
        System.out.println("Name is " + nonOwnerProxy.getName());
        System.out.println("before set interests:" + nonOwnerProxy.getInterests());
        try {
            // 嘗試修改他人興趣
            nonOwnerProxy.setInterests("painting");
        } catch (Exception e) {
            System.out.println("can't set interests from non owner proxy");
        }
        System.out.println("after set interests:" + nonOwnerProxy.getInterests());
        nonOwnerProxy.setHotOrNotRating(5);
        System.out.println("rating is " + nonOwnerProxy.getHotOrNotRating());
    }

    /**
     * 獲取用戶處理自己信息的代理對象
     * 
     * 此方法需要一個PersonBean對象作爲參數,然後返回
     * 他的代理,因爲代理和被代理對象(主題subject)
     * 實現了相同的接口,該方法最終返回一個PersonBean
     * 
     * @param personBean
     * @return
     */
    public PersonBean getOwnerProxy(PersonBean personBean) {
        return (PersonBean) Proxy.newProxyInstance( // 通過Proxy類的靜態方法newProxyInstance創建代理
                personBean.getClass().getClassLoader(),  // 將PersonBean的類載入器當作參數
                personBean.getClass().getInterfaces(),  // 代理需要實現的接口
                new OwnerInvocationHandler(personBean)); // 處理器
    }

    /**
     * 獲取用戶處理他人信息的代理對象
     * 
     * @param personBean
     * @return
     */
    public PersonBean getNonOwnerProxy(PersonBean personBean) {
        return (PersonBean) Proxy.newProxyInstance(
                personBean.getClass().getClassLoader(), 
                personBean.getClass().getInterfaces(), 
                new NonOwnerInvocationHandler(personBean));
    }

}

執行結果:
這裏寫圖片描述

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