代理模式Proxy深入 动态代理

前一篇文章中介绍了代理模式的概念和一个基本案例代理模式Proxy入门 之帮你追求心仪的姑娘,接下来继续深入学习代理模式一个变种,动态代理。

首先来回顾下代理概念:为一个对象提供代表以控制该对象的访问。上一文案例中代理仅仅是做到代理访问对象的方法,而动态代理是一种根据访问权限决定客户是否可访问对象的代理

UML图

这里写图片描述

使用Java API的代理,创建一个动态代理(代理保护)

动态代理是在运行时动态创建的一个代理类,它不会做任何实际的工作,会在生成时需要指定一个InvocationHandler对象,当代理对象方法的调用时,通过在InvocationHandler的invoke方法中做实际操作,换言之,InvocationHandler的工作就是响应代理的任何调用。

java动态代理类位于java.lang.reflect包下,API文档说明
InvocationHandler 是代理实例的调用处理程序 实现的接口。
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。

案例:社交平台会员管理

需求:平台上有多个会员,每个会员需要填写的自己的资料,查看其他人的资料,互相之间可以印象评分,但是不可以给自己评分,也不能修改其他人的资料。

  • 定义会员行为,抽取接口
/** 定义会员接口 */
public interface IPersonBean {

    /** 获取姓名 */
    String getName();

    /** 获取性别 */
    String getGender();

    /** 获取兴趣爱好 */
    String getInterests();

    /** 获取会员评分 */
    int getRatting();

    void setName(String name);

    void setGender(String gender);

    void setInterests(String interests);

    void setRatting(int ratting);
}
  • 实现会员实体类
/** 会员实现类 */
public class PersonBeanImp implements IPersonBean {

    private String name;
    private String gender;
    private String interests;
    private int ratting;

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

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

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

    @Override
    public int getRatting() {
        return ratting;
    }

    @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 setRatting(int ratting) {
        this.ratting = ratting;
    }

    public PersonBeanImp() {
        super();
    }

    public PersonBeanImp(String name, String gender, String interests,
            int ratting) {
        super();
        this.name = name;
        this.gender = gender;
        this.interests = interests;
        this.ratting = ratting;
    }

}
  • 会员对自己资料是“拥有者”,我们先实现拥有者对象实例的调用处理程序,首先实现InvocationHandler 接口,复写invoke方法,在invoke方法根据逻辑判断访问权限。此处会员可以访问自己的所有信息,并且可以修改除了评分以外的信息。所以在invoke方法中,当用户尝试修改自己的评分是不允许,则抛出IllegalAccessException,具体见完整代码。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 拥有者对象实例的调用处理程序
 * 
 * InvocationHandler 是代理实例的调用处理程序 实现的接口。
 * 
 * 每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
 */
public class OwnerInvocationHandler implements InvocationHandler {
    private IPersonBean personBean;

    public OwnerInvocationHandler(IPersonBean personBean) {
        super();
        this.personBean = personBean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        try {
            if (method.getName().startsWith("get")) {
                // 会员可以获得所有 自己的信息
                return method.invoke(personBean, args);
            } else if (method.getName().equals("setRatting")) {
                // 会员没有权限 修改自己的评分
                throw new IllegalAccessException();
            } else if (method.getName().startsWith("set")) {
                // 有权限修改除了评分以外的其他信息
                return method.invoke(personBean, args);
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

}

同理,访问其他会员信息的调用处理程序如下:

/**
 * 被拥有者对象实例的调用处理程序
 * 
 * 可以查看其他会员的所有信息,并且可以打分,但是不允许修改其他个人信息
 */
public class NonOnwerInvocationHandler implements InvocationHandler {

    private IPersonBean personBean;

    public NonOnwerInvocationHandler(IPersonBean personBean) {
        super();
        this.personBean = personBean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        try {
            if (method.getName().startsWith("get")) {
                // 可以查看其他会员的所有信息
                return method.invoke(personBean, args);
            } else if (method.getName().equals("setRatting")) {
                // 可以打分
                return method.invoke(personBean, args);
            } else if (method.getName().startsWith("set")) {
                // 不允许修改其他个人信息
                throw new IllegalAccessException();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
  • 动态创建代理,实例对象类和 调用处理程序 都定义好了,接下来就定义我们的 动态代理。
public IPersonBean getOwnerProxy(IPersonBean personBean) {
        // 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
        return (IPersonBean) Proxy.newProxyInstance(personBean.getClass()
                .getClassLoader(), personBean.getClass().getInterfaces(),
                new OwnerInvocationHandler(personBean));
    }

利用Proxy的静态方法newProxyInstance创建代理


public static Object newProxyInstance(ClassLoader loader,
                                Class<?>[] interfaces,
                                      InvocationHandler h)
                               throws IllegalArgumentException
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于: 
     Proxy.getProxyClass(loader, interfaces).
         getConstructor(new Class[] { InvocationHandler.class }).
         newInstance(new Object[] { handler });
 Proxy.newProxyInstance 抛出 IllegalArgumentException,原因与 Proxy.getProxyClass 相同。 
参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序 
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口 
抛出: 
IllegalArgumentException - 如果违反传递到 getProxyClass 的参数上的任何限制 
NullPointerException - 如果 interfaces 数组参数或其任何元素为 null,或如果调用处理程序 h 为 null
  • 准备就绪,开始测试吧
import java.lang.reflect.Proxy;

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

        DynamicProxyTest dynamicProxyTest = new DynamicProxyTest();
         dynamicProxyTest.ownerTest();
        dynamicProxyTest.nonOwnerTest();
    }

    private void ownerTest() {
        // 初始化一个 李狗蛋 的会员对象
        IPersonBean personBean = new PersonBeanImp("李狗蛋", "男", "吃饭睡觉打豆豆", 6);
        IPersonBean ownerProxy = getOwnerProxy(personBean);
        System.out.println("name:" + ownerProxy.getName());
        System.out.println("gender:" + ownerProxy.getGender());
        System.out.println("interests:" + ownerProxy.getInterests());
        System.out.println("ratting:" + ownerProxy.getRatting());

        System.out.println("----------------修改个人信息----------------");
        ownerProxy.setName("李白"); // 狗蛋不好听,改个名
        ownerProxy.setInterests("吟诗作对,品美酒");

        System.out.println("name:" + ownerProxy.getName());
        System.out.println("gender:" + ownerProxy.getGender());
        System.out.println("interests:" + ownerProxy.getInterests());
        System.out.println("ratting:" + ownerProxy.getRatting());

        System.out.println("----------------修改评分----------------");
        // 嫌弃其他会员给的分太低了,自己给该高点,来个满分10分吧
        try {
            ownerProxy.setRatting(10);
        } catch (Exception e) {
            System.err.println("无权限修改自己的评分");
        }
        System.out.println("ratting:" + ownerProxy.getRatting());
    }

    private void nonOwnerTest() {
        // 初始化一个 李狗蛋 的会员对象
        IPersonBean personBean = new PersonBeanImp("李狗蛋", "男", "吃饭睡觉打豆豆", 6);
        IPersonBean nonOwnerProxy = getNonOwnerProxy(personBean);
        System.out.println("name:" + nonOwnerProxy.getName());
        System.out.println("gender:" + nonOwnerProxy.getGender());
        System.out.println("interests:" + nonOwnerProxy.getInterests());
        System.out.println("ratting:" + nonOwnerProxy.getRatting());

        System.out.println("----------------修改评分----------------");
        nonOwnerProxy.setRatting(10);
        // 我来给狗蛋打个高分,来个满分10分吧
        System.out.println("ratting:" + nonOwnerProxy.getRatting());

        System.out.println("----------------修改狗蛋的信息----------------");
        nonOwnerProxy.setName("李白"); // 狗蛋不好听,改个名
        nonOwnerProxy.setInterests("吟诗作对,品美酒");
        System.out.println("name:" + nonOwnerProxy.getName());
        System.out.println("interests:" + nonOwnerProxy.getInterests());

    }

    public IPersonBean getOwnerProxy(IPersonBean personBean) {
        // 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
        return (IPersonBean) Proxy.newProxyInstance(personBean.getClass()
                .getClassLoader(), personBean.getClass().getInterfaces(),
                new OwnerInvocationHandler(personBean));
    }

    public IPersonBean getNonOwnerProxy(IPersonBean personBean) {
        // 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
        return (IPersonBean) Proxy.newProxyInstance(personBean.getClass()
                .getClassLoader(), personBean.getClass().getInterfaces(),
                new NonOnwerInvocationHandler(personBean));
    }
}

这里写图片描述

观察结果:当我们尝试通过动态代理修改自己的评分时,OwnerInvocationHandler 调用处理程序 invoke方法中,通过反射判断该方法不无权限访问,返回异常,尝试给自己修改为10分,由于不满足可最终结果任然是6分。

此时我们动态代理完成了根据权限控制对象访问的目标,好了,完成,哎呀夜已深,好困,小哥去睡了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值