文章目录
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,代理会将方法的调用委托给真实对象处理,但在真实对象执行该方法前后,代理可以增加功能,从而非侵入式扩展了真实对象的功能