设计模式 - D10 - 代理模式

代理模式

定义

代理模式:为另一个对象提供一个替身或占位符以控制对这个对象的访问

使用代理模式创建代表,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。
代理控制访问的方式:

  • 远程代理控制访问远程对象
  • 虚拟代理控制访问创建开销大的资源
  • 保护代理基于权限控制对资源的访问
    在这里插入图片描述
    Subject:为RealSubject和Proxy提供接口,通过实现同一接口,Proxy在RealSubject出现的地方取代它
    RealSubject:真正做事的对象,被proxy代理和控制访问
    Proxy:持有RealSubject的引用,甚至负责其创建与销毁;客户与RealSubject之间的交互必须通过Proxy。

远程代理

远程代理好比“远程对象的本地代表”,其中“远程对象”指活在不同JVM堆中的对象,“本地代表”指由本地方法调用的对象,其行为会转发到远程对象中
客户对象所做的事就像在做远程方法调用,但其实只是调用本地堆中的“代理”对象上的方法,再有代理处理所有网络通信的低层细节
但是我们不能取得另一个堆中的对象引用,即:Duck d = <另一个堆的对象>是无效的,变量d只能引用当前代码语句的同一堆空间的对象;解决方法:RMI-Java远程方法调用

远程方法

当我们想要设计一个系统能够调用本地对象,然后将每个请求转发到远程对象上进行时,我们需要一些辅助对象帮助我们进行真正的沟通

  • 客户辅助对象:联系远程服务器,传送方法调用信息(如:方法名、变量…),然后等待服务器返回。对于客户对象来说,客户辅助对象就像是真正的服务,但其实它只负责转发请求。
  • 服务辅助对象:从客户辅助对象中接收请求(Socket链接),将调用信息解包,然后调用真正服务对象上的真正方法;当服务辅助对象获得返回值时,将返回值打包再相应给客户辅助对象(Socket输出流)。对于服务对象来说,调用是本地的,来自服务辅助对象而不是远程客户。
    在这里插入图片描述

Java RMI

RMI提供了客户辅助对象和服务辅助对象,为客户辅助辅助对象创建和服务对象相同的方法,并提供了用于寻找和访问远程对象的查找服务。RMI将客户辅助对象称为Stub-“桩”,服务辅助对象称为Skelteton-“骨架”
由于客户辅助对象会通过网络发送方法调用,而网络和IO是有风险且容易失败的,因此随时可能抛出异常

制作远程服务

  1. 制作远程接口
    远程接口定义出可以让客户远程调用的方法,客户再将其作为服务的类类型。Stub和实际的服务都是先此接口
// 1. 扩展Remote接口,这是一个无方法的标记接口
public interface MyRemote extends Remote {
    // 2. 声明所有方法都会抛出RemoteException,以防网络及IO的异常
    // 3. 确定变量和返回值是属于原语类型或可序列化类型(实现Serializable接口)
    //      - 对象通过网络运回必须可序列化
    public String sayHello() throws RemoteException;
}
  1. 制作远程实现
    这是实际工作的类,为远程接口中定义的远程方法提供了真正的实现,这就是客户真正想要调用方法的对象(如:GumballMachine)
// 2. 扩展UnicastRemoteObject,以提供远程功能
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{
    // 1. 实现远程接口MyRemote
    @Override
    public String sayHello() throws RemoteException {
        return "Server says, 'Hey'";
    }

    // 3. 设计一个不带变量的构造器并声明RemoteException
    //      - 结局超类UnicastRemoteObject的构造器抛出RemoteException的问题
    public MyRemoteImpl() throws RemoteException {

    }
}
try{
  	// 利用RMI注册服务(需要先开启一个终端执行rmiregistry,见第4步)
	MyRemote service = new MyRemoteImpl();
  	Naming.rebind("RemoteHello", service);
} catch (Exception ex) { ... }
  1. 利用rmic产生stub和skeleton
    这是客户和服务的辅助类,我们不需要自己创建;当运行rmic工具时,会自动处理
// 在MyRemoteImpl.class存放目录下执行rmic命令,生成stub和skeleton
rmic MyRemoteImpl
  1. 启动RMI registry(rmiregistry)
    rmiregistry就像是电话簿,客户可以从中查找到代理的位置(也就是客户的stub helper对象)
// 开启一个终端执行rmiregistry
rmiregistry
  1. 开始远程服务
    必须让服务对象开始运行:服务实现类会去实例化一个服务的实例,并将这个服务注册到RMI registry;注册后,这个服务就可以供客户调用
// 开启另一个终端,启动服务:可从一个远程实现类中的main()方法或一个独立的启动类中启动
public class ProxyTestDrive {
    public static void main(String[] args) {
        try {
            MyRemote lookup = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
            System.out.println(lookup.sayHello());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:

  • 启动远程服务之前必须先启动rmiregistry(要用Naming.rebind()注册服务,rmiregistry必须是运行的)
  • 变量和返回值类型必须是可序列化类型(实现Serializable接口,transient关键字可告诉JVM不要序列化某个字段)
  • 必须为客户提供stub类(skeleton不再必须)

在这里插入图片描述

虚拟代理

虚拟代理作为创建开销大的对象的代表,经常在我们真正需要一个对象时才创建这个开销大的真实对象。在真实对象的创建前和创建中时,虚拟代理扮演该对象的替身;创建后,代理就会将请求直接委托给对象
在这里插入图片描述

示例

假设现在我们需要在一个应用程序上加载某个图像,因为加载图像是一个开销较大的工作,所以我们可以利用虚拟代理在图像加载未完成时显示一些信息,如:“图像加载中”,然后再加载完成后,将显示责任委托给创建出来的图像对象
工作流程

  • ImageProxy创建一个ImageIcon,然后从网络URL上加载图像
  • 加载过程中,ImageProxy显示"图像加载中"
  • 当图像加载完毕,ImageProxy吧所有方法调用委托给真正的ImageIcon,这些方法包括了paintIcon()、getWidth()和getHeight()
  • 如果用户请求新的图像,就创建新的代理,重复这样的过程
public class ImageProxy implements Icon {

    ImageIcon imageIcon;
    URL imageUrl;
    Thread retrievalThread;
    boolean retrieving = false;

    public ImageProxy(URL url) {
        imageUrl = url;
    }

    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
        if (imageIcon != null) {
            imageIcon.paintIcon(c, g, x, y);
        } else {
            g.drawString("Loading CD cover, please wait...", x+300, y+300);
            if (!retrieving) {
                retrieving = true;
                retrievalThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            imageIcon = new ImageIcon(imageUrl,"CD Cover");
                            c.repaint();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                retrievalThread.start();
            }
        }
    }

    @Override
    public int getIconWidth() {
        if (imageIcon != null) {
            return imageIcon.getIconWidth();
        } else {
            return 800;
        }
    }

    @Override
    public int getIconHeight() {
        if (imageIcon != null) {
            return imageIcon.getIconHeight();
        } else {
            return 600;
        }
    }
}

保护代理

动态代理

Java的动态代理提供了在运行时动态创建一个代理类、实现一个或多个接口并将方法的调用转发刀所指定的类的支持
在这里插入图片描述

  • Proxy:由Java产生,且实现了完整的Subject接口
  • InvocationHnadler:Proxy上的任何方法调用都会传入此类,控制对RealSubject的访问

假设,我们现在有一个PersonBean接口和其实现类PersonBeanImpl

public interface PersonBean {
    String getName();
    String getGender();
    String getInterests();
    int getHotOrNotRating();

    void setName(String name);
    void setGender(String gender);
    void setInterests(String interests);
    void setHotOrNotRating(int rating);
}
public class PersonBeanImpl implements PersonBean{

    String name;
    String gender;
    String interests;
    int rating;
    int ratingCount;

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

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

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

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

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

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


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

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

显然,由于PersonImpl上的方法都是公有的,因此任何人只要获取到一个PersonBeanImpl对象,就能修改数据;对此我们可以使用保护代理,根据访问权限,决定客户可否访问对象。

创建InvocationHandler

我们需要创建两个InvocationHandler,一个给拥有者使用,另一个给非拥有者使用
InvocationHandler:当代理的方法被调用时,代理就会把这个调用转发给InvocationHandler,但这并不是通过调用IncovationHandler的相应方法做到的,其原理如下
在这里插入图片描述

public class OwnerInvocationHandler implements InvocationHandler {
    
    PersonBean person;
    
    public OwnerInvocationHandler(PersonBean person) {
        this.person = person;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (method.getName().startsWith("get")) {
                return method.invoke(person, args);
            } else if (method.getName().equals("setHotOrNotRating")) {
                throw new IllegalAccessException();
            } else if (method.getName().startsWith("set")) {
                return method.invoke(person, args);
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}

创建Proxy类并实例化Proxy对象

public class ProxyFactory {
    public static PersonBean getOwnerProxy(PersonBean person) {
        return (PersonBean) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new OwnerInvocationHandler(person));
    }
}

测试

public class ProxyDriveTest {
    public static void main(String[] args) {
        PersonBean person = new PersonBeanImpl();
        person.setGender("male");
        person.setHotOrNotRating(1);
        person.setInterests("running");
        person.setName("Tom");

        PersonBean ownerProxy = ProxyFactory.getOwnerProxy(person);
        try{
           ownerProxy.setName("Tom1");
           System.out.println("Set Success");
        } catch (Exception e) {
            System.out.println("You can't set");
        }
        try {
            System.out.println(ownerProxy.getGender());
        } catch (Exception e) {
            System.out.println("You can't get");
        }
        try {
            ownerProxy.setHotOrNotRating(2);
            System.out.println("Set Success");
        } catch (Exception e) {
            System.out.println("You can't change rating");
        }
    }
}

输出:
Set Successmale
You can’t change rating
可见,权限控制生效,无法修改HotOrNotRating,但其他访问或修改是成功的

其他代理

  • 防火墙代理:控制网络资源的访问,保护主题免于“坏客户”的侵害
  • 智能引用代理:当主题被引用时,进行额外地动作,例如计算一个对象被引用的次数
  • 缓存代理:为开销大的运算结果提供暂时存储;同时也允许多个客户共享结果,以减少计算或网络延迟
  • 同步代理:多线程的情况下为主题提供安全访问
  • 复杂隐藏代理:用来隐藏一个类的复杂集合的复杂度,并惊醒访问控制,有时也称为外观代理(区分外观模式,外观代理控制访问,外观模式只提供另一组接口)
  • 写入时复制代理:用来控制对象的复制,方法是延迟对象的复制,直到客户真正需要为止(虚拟代理的变体)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值