【-1】README
-1.1)本文部分文字描述转自“head first 设计模式”,旨在学习 远程代理对象 的基础知识;
-1.2)多线程实现糖果自动售卖机监控程序为原创;
-1.3)博文最后,转载了代理模式的定义;
-1.4)本文中的代码用到了状态模式中的“糖果自动哦售卖机”的中奖事件,for spec info ,please visit http://blog.csdn.net/pacosonswjtu/article/details/50986380
【0】需求描述
1)problem+solution:
1.1)problem:问题在于监视器和糖果机在同一个 jvm 上面执行,而售卖机的boss的需求是远程监控糖果售卖机,希望在其桌面上远程监控这些机器。
1.2)solution:所以我们可以不要变化 CandyMachineMonitor , 不要将糖果机交给 monitor,而是将一个远程对象的代理交给他;
(干货——引入远程代理)
2)远程代理和本地代表:这是一种对象,活在不同的jvm 堆中(更确切地说,在不同的地址空间运行的远程对象)。本地代表:这是一种可以由本地方法调用的对象,其行为会转发到远程对象中;
【1】远程代理技术相关
1)RMI提供了: 客户辅助对象,称为stub(桩)和服务辅助对象,称为skeleton(骨架);
(干货——stub==客户辅助对象, 而skeleton==服务辅助对象)
2)制作远程服务(换句话说,这些步骤将一个普通的对象变成可以被远程客户调用的远程对象)
- step1)制作远程接口: 远程接口定义出可以让客户远程调用的方法;
- step2)制作远程实现:为远程接口中定义的远程方法提供真正的实现;
- step3)其中RMI registry(rmiregistry): rmiregistry如同电话簿,客户可以从中查找到代理的位置;
- step4)开始远程服务:你必须让服务对象开始运行。你的服务实现类会去实例化一个服务的实例,并将这个服务注册到RMI registry。注册之后,这个访问就可以供客户调用了;
3)制作远程服务(代码实例)
(for downloading source code, please visithttps://github.com/pacosonTang/HeadFirstDesignPattern/tree/master/chapter11_proxyPattern/chapter11_rmi)
3.1)制作远程接口(服务端)
3.2)制作远程实现(服务端)
- step1)实现远程接口:你的访问必须要实现远程接口,也就是客户将要调用的方法的接口;
- step2)扩展 UnicastRemoteObject:为了要称为远程服务对象,你的对象需要某些远程的功能,最简单的方式就是扩展 UnicastRemoteObject;
- step3)设计一个不带变量的构造器,并声明为 RemoteException: 你的超类UnicastRemoteObject 带来一个小问题: 它的构造器抛出一个 RemoteException。解决方法是 为你的远程实现声明一个构造器,这样就有了一个声明 RemoteException的地方。
// 制作远程实现
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{
private static final long serialVersionUID = 2494818195984623711L;
protected MyRemoteImpl() throws RemoteException {
super();
}
@Override
public String sayHello() throws RemoteException {
return "every body say, xiao tang tang";
}
public static void main(String[] args) {
try {
MyRemote service = new MyRemoteImpl();
Naming.rebind("hehe", service);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- step4)用RMI Registry 注册此服务 : 现在你已经有一个远程服务了,必须让它可以被远程客户调用。你要做的就是将此服务实例化,然后放进RMI registry中。当注册这个实现对象时,RMI 系统其实注册的是stub,因为这是客户真正需要的。注册服务使用了 java.rmi.Naming 类的静态rebind()方法(如上述代码);
3.3)执行rmiregistry: 开启一个终端,启动 rmiregistry;
3.4)启动服务:从哪里启动?可能是从你的远程实现类中的main()方法,也可能是从一个独立的启动类;
4)客户如何取得stub对象?
4.1)客户必须取得stub对象(代理)以调用其中的方法,所以我们就需要 RMI Registry的帮忙。
4.2)客户取得stub对象的steps:
Attention)对于RMI, coders 最常犯的三个错误:
- A1)忘了在启动远程服务前先启动 rmiregistry;
- A2)忘了让变量和返回值的类型称为可序列化的类型;
- A3)忘了给客户提供stub类;
【2】将代理模式应用到糖果自动售卖机的监控系统中(多线程实现监控)
(for download, please visit https://github.com/pacosonTang/HeadFirstDesignPattern/tree/master/chapter11_proxyPattern/chapter11_proxy)
2.1)创建一个远程接口
// 创建远程服务接口(服务器接口)
public interface CandyMachineRemote extends Remote{
int getCount() throws RemoteException;
String getLocation() throws RemoteException;
State getState() throws RemoteException;
}
2.2)提供该远程接口的实现类
public class CandyMachineProxyServer {
public static void main(String[] args) throws RemoteException {
CandyMachine machine = new CandyMachine("chengdu", 50);
try {
Naming.rebind("machine", machine);
} catch (MalformedURLException e) {
e.printStackTrace();
}
new Thread(new Runnable() { // 开启一个线程实现client循环投币
@Override
public void run() {
System.out.println("\n\n ====== 下面进入循环测试(中奖率为20%) ======");
for (int i = 0; i < 5; i++) {
machine.insertQuarter();
machine.turnCrank();
System.out.println(machine);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();;
}
}
2.3)启动rmi registry 注册表服务;
E:\bench-cluster\cloud-data-preprocess\designPattern\src>rmiregistry
2.4)将实现类添加到 注册表服务以便client 获取它;
E:\bench-cluster\cloud-data-preprocess\designPattern\src>java com.designpattern.chapter11_proxy.CandyMachineProxyServer
2.5)编写监控器程序
public class CandyMachineProxyMonitor {
CandyMachineRemote remoteMachine;
public CandyMachineProxyMonitor(CandyMachineRemote remoteMachine) {
this.remoteMachine = remoteMachine;
}
public void report() {
int counter = 0;
try {
while(true) {
System.out.println("round" + (++counter));
System.out.println("machine.location = " + remoteMachine.getLocation());
System.out.println("machine.count = " + remoteMachine.getCount());
System.out.println("machine.state = " + remoteMachine.getState());
if(counter == 5) {
break;
}
Thread.sleep(10000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.5)client 获取该远程对象,之后,就如一般对象那样使用(client==CEO==桌面监视器);
<pre name="code" class="java">//client
public class CandyMachineProxyMonitorTest {
public static void main(String[] args) throws RemoteException {
CandyMachineRemote remoteMachine;
try {
remoteMachine = (CandyMachineRemote) Naming.lookup("rmi://127.0.0.1/machine");
CandyMachineProxyMonitor monitor = new CandyMachineProxyMonitor(remoteMachine);
monitor.report();
} catch (MalformedURLException | NotBoundException e) {
e.printStackTrace();
}
}
}
打印结果)
【3】代理模式
3.1)定义:代理模式为另一个对象提供了一个替身或占位符以控制对这个对象的访问;
3.2)使用代理模式创建代表对象:让代表对象控制某对象的访问,被代理的对象可以是远程对象、创建开销大的对象或 需要安全控制的对象;