《HF 设计模式》 C11 代理模式

1 代理模式

1.1 给糖果机加上监控

在C10的状态模式中,我们设计了一个能随着状态改变而改变行为的糖果机,现在收到了新的需求,要求为糖果机加上一个监控的功能,监测当前糖果机的状态:

/**
 * @author 雫
 * @date 2021/3/11 - 12:27
 * @function 糖果机
 */
public class CandyMachine {
    private String location;
    private int count;
    private String state;

    public CandyMachine(String location, int count) {
        this.location = location;
        this.count = count;
        this.state = "WAITING";
    }

    public String getLocation() {
        return location;
    }

    public int getCount() {
        return count;
    }

    public String getState() {
        return state;
    }
	...
}


/**
 * @author 雫
 * @date 2021/3/11 - 12:30
 * @function 糖果机监控
 */
public class CandyMachineMonitor {
    private CandyMachine machine;

    public CandyMachineMonitor(CandyMachine machine) {
        this.machine = machine;
    }

    public void report() {
        System.out.println("糖果机位置:" + machine.getLocation());
        System.out.println("糖果余量:" + machine.getCount());
        System.out.println("糖果机状态:" + machine.getState());
    }

}

在这里插入图片描述
这样虽然能实现监控糖果机当前状态的功能,但是真正需要的是在远程监控糖果机,即Monitor类和Machine类不在一个JVM上运行,也就是说根本获取不到实例化Monitor所需的Machine对象

1.2 远程代理(Remote Proxy)

现在糖果机和监控器不在一个JVM上,但是监控器需要一个真实的糖果机对象才能工作,为此我们为糖果机创建一个代理对象,把代理对象交给监控器,这个代理对象像是一个糖果机对象,但实际上它在本地,只是利用网络和一个在远程的真正的糖果机沟通
在这里插入图片描述
我们要获取糖果机的状态,本质是调用远程糖果机中的get方法,即远程方法调用,有了代理后,获取糖果机的状态是调用了本地代理对象上的方法,由代理处理所有网络通信的底层细节

如现在要获取远程糖果机的剩余糖果数量,我们调用代理对象的getCount()方法,代理对象将通过网络调用远程糖果机的getCount()方法,远程糖果机将结果通过网络返回给代理,代理将结果返回给监控器
在这里插入图片描述
现在我们需要:

1,为CandyMachine提供一些可以被远程调用的方法

2,创建一个能和CandyMachine沟通的代理Proxy

3,让CandyMonitor去调用代理Proxy中提供的方法

但在实现前,需要先了解RMI(远程方法调用)

1.3 远程方法调用(RMI)

在这里插入图片描述
现在我们为本地客户配备一个客户辅助对象,客户辅助对象提供一些方法供本地客户调用,客户辅助对象假装成远程服务对象,假装自己有客户需要调用的方法

我们再为远程服务对象配备一个服务辅助对象,服务辅助对象与客户辅助对象通过Socket交流,服务辅助对象用来调用远程服务对象中的方法

当客户调用客户辅助对象中的方法时,客户辅助对象会联系服务辅助对象,将要调用的方法信息(方法名,变量等)通过Socket传送给服务辅助对象,服务辅助对象收到后,通过这些信息调用远程服务对象中真正的方法,将返回值再通过Socket送给客户辅助对象,客户辅助对象再将该返回值送给客户

JavaRmi:
在这里插入图片描述
JavaRmi提供了客户辅助对象和服务辅助对象,为客户辅助对象创建和服务对象相同的方法,使用JavaRmi可以不用写任何网络或者I/O的代码,但是网络和I/O是存在的,因此网络和I/O的风险也是不可避免的

1.4 远程代理

1,制作方法接口
该接口内的方法就是客户端想要调用远程服务中的方法:

/**
 * @author 雫
 * @date 2021/3/11 - 14:45
 * @function 方法接口
 * 客户端想要调用远程服务的方法
 */
public interface MyRemote extends Remote {

    public String sayHello() throws RemoteException;
}

2,实现方法接口
即远程服务对客户端想要调用的方法的具体实现:

/**
 * @author 雫
 * @date 2021/3/11 - 14:46
 * @function
 */
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {

    protected MyRemoteImpl() throws RemoteException {
        super();
    }

    @Override
    public String sayHello() throws RemoteException {
        return "Hello!";
    }
}

3,远程服务器端
将方法的实现类注册到Rmi registry中,等待来自客户端的调用:

/**
 * @author 雫
 * @date 2021/3/11 - 14:47
 * @function
 */
public class Server {

    public static void main(String[] args) {
        try {
            MyRemote service = new MyRemoteImpl();
            LocateRegistry.createRegistry(12345);
            Naming.rebind("rmi://localhost:12345/RemoteHello", service);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4,客户端
从Rmi registry中找到需要调用的方法所在的实现类,接着远程调用方法:

/**
 * @author 雫
 * @date 2021/3/11 - 14:48
 * @function
 */
public class Client {

    public static void main(String[] args) {
        try {
            MyRemote myRemote = (MyRemote) Naming.lookup("rmi://localhost:12345/RemoteHello");
            String s = myRemote.sayHello();
            System.out.println(s);
        } catch (MalformedURLException | RemoteException | NotBoundException e) {
            e.printStackTrace();
        }
    }

}

客户端调用远程服务的sayHello()方法:
在这里插入图片描述
JavaRmi的工作方式:
在这里插入图片描述
核心是Rmi registry,Rmi registry像电话簿一样,客户通过lookup()方法从Rmi registry中找到Stub(代理),接着客户调用代理中的方法,代理会请求服务器执行真正的方法,服务器执行完后,将结果返回

1.5 定义代理模式

代理模式
为一个对象提供一个“替身”以控制对该对象的访问

刚才用到的远程代理就是代理模式的一种实现,代理对象作为糖果机对象的替身,从而让监视器间接地和糖果机沟通,使监视器不需要再关注网络,I/O等实现

还有其它的变形:

1,远程代理 控制访问 远程对象

远程代理可以作为另一个JVM上对象地本地代表
调用代理的方法,会被代理用网络转发到远程执行
并将结果通过网络返回给代理,再由代理将结果转给客户

2,虚拟代理 控制访问 创建开销大的对象

虚拟代理作为创建开销大的对象的代表
虚拟代理经常直到我们真正需要一个对象时才创建它
当对象在创建前和创建中时,由虚拟代理扮演对象的替身
对象创建后,代理就会将请求委托给对象

3,保护代理 控制访问 需要安全控制的对象

使用代理模式创建代理对象,让代理对象控制对某对象的访问,被代理的对象可以是远程的对象,创建开销大的对象或需要安全控制的对象
在这里插入图片描述
即我们让代理对象和被代理对象实现同一个接口,通过组合把被代理对象当作代理对象的成员,这样客户就可以把代理对象当作被代理对象使用,当用户调用某个方法时,代理对象可以在该方法被委托给被代理对象执行前后增加一些操作,从而增加新的功能如 权限检测,安全判断,远程调用

1.6 虚拟代理

当我们需要生成一个开销比较大的对象时,可以采用虚拟代理来暂时代替这个对象,当该对象生成后,再将职责交给真正的对象

考虑如下简单的场景:
如现在要生成一个HashMap,生成时要向其中存储大量的键值对,可能需要一定的时间,我们希望能给用户在这段时间内显示一些其它的信息,或执行其它的方法,就可以采用虚拟代理:

代理对象和被代理对象的共同接口:

/**
 * @author 雫
 * @date 2021/3/11 - 17:06
 * @function 代理和被代理的共同接口
 */
public interface MyMap {

    HashMap<Integer, Integer> getMyMap();
}

代理:

/**
 * @author 雫
 * @date 2021/3/11 - 17:07
 * @function 代理
 * 控制访问RealMyMap
 */
public class MyMapProxy implements MyMap {

    private RealMyMap realMyMap;

    public MyMapProxy() {
        this.realMyMap = new RealMyMap();
    }

    @Override
    public HashMap<Integer, Integer> getMyMap() {
        System.out.println("正在生成HashMap...");
        HashMap<Integer, Integer> map = realMyMap.getMyMap();
        System.out.println("HashMap已生成!");
        return map;
    }

}

被代理对象:

/**
 * @author 雫
 * @date 2021/3/11 - 17:07
 * @function 被代理类
 * 真正生成HashMap
 */
public class RealMyMap implements MyMap {
    private HashMap<Integer, Integer> map;

    public RealMyMap() {
        this.map = new HashMap<>();
    }

    @Override
    public HashMap<Integer, Integer> getMyMap() {

        for(int i = 0; i < 1000; i++) {
            this.map.put(i, i+1);
        }
        return map;
    }

}

测试:
在这里插入图片描述
虽然这个例子很简单,但是我们在不修改被代理类的情况下,为它增加了新的功能,考虑另外一些场景,当我们从网上下载图片时,由于带宽和网络负载等限制,图片可能需要一些时间才能下好,这时就可以采用虚拟代理,在屏幕前显示一些字样,或者一些提前准备好的图片,当图片下载好之后,再打印真正的图片

1.7 保护代理

上面的虚拟代理本质上是静态的,我们为被代理类创建了一个代理类,在代理类中控制了对被代理类的访问,更为常用和便捷的是动态代理,动态代理之所以成为动态,是因为运行时才将它的类创建出来,代码刚开始执行时,还没有Proxy类

在这里插入图片描述
我们用JDK的动态代理来完成一个保护代理,本质是执行方法前,对权限等因素进行检测,从而判断是否执行该方法

代理接口及实现:

/**
 * @author 雫
 * @date 2021/3/11 - 18:28
 * @function 动态代理接口
 */
public interface Person {

    void setName(String name);

    String getName();

    void setAge(int age);

    int getAge();

}

/**
 * @author 雫
 * @date 2021/3/11 - 18:30
 * @function 接口的实现
 */
public class PersonImpl implements Person {
    private String name;
    private int age;

    public PersonImpl(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

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

    @Override
    public int getAge() {
        return age;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

}

获取动态代理:

/**
 * @author 雫
 * @date 2021/3/12 - 13:36
 * @function 获取动态代理
 */
@SuppressWarnings("all")
public class GetProxy {

    public GetProxy() {}

    public static <T> T getProxy(Object object) {
        Class<?> klass = object.getClass();

        ClassLoader classLoader = klass.getClassLoader();
        Class<?>[] interfaces = klass.getInterfaces();

        return (T) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if(method.getName().startsWith("set")) {
                    System.out.println("禁止更改数据");
                    return null;
                }

                Object result = method.invoke(object, args);

                System.out.println("执行操作成功");

                return result;
            }
        });
    }

    public static <T> T getProxy(Class<?> klass) throws IllegalAccessException, InstantiationException {
        return getProxy(klass.newInstance());
    }

}

测试:
在这里插入图片描述

1.8 代理的更多应用场景

防火墙代理
控制网络资源的访问,避免非法操作

智能引用代理
当被代理对象被引用时,进行额外的动作,如计算一个对象被引用的次数

缓存代理
为开销大的运算结果提供暂时存储,允许多个客户共享结果,以减少计算或网络延迟

同步代理
在多线程环境下,为被代理对象提供安全的访问

复杂隐藏代理
用来隐藏一个类的复杂集合的复杂度,并进行访问控制

写入时复制代理
用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止

1.9 代理模式小结

1,代理模式为真实对象提供替身,以便管理客户对真实对象的访问

2,远程代理管理客户和远程对象之间的交互

3,虚拟代理控制访问实例化开销大的对象

4,保护代理基于调用者控制对象方法的访问

5,代理在结构上类似装饰者,但目的不同,装饰者模式是为对象加上行为,而代理是为真实对象增加控制访问

6,为了让客户使用代理而不是真实的对象,可以使用工厂模式,返回代理即可

7,代理会将方法的调用委托给真实对象处理,但在真实对象执行该方法前后,代理可以增加功能,从而非侵入式扩展了真实对象的功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值