【设计模式】代理模式(动态代理)

前言:本篇文章为阅读《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));
    }

}

执行结果:
这里写图片描述

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