本节内容主要来自Head First
【what】
代理模式是什么?
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
首先来一个简单的图,有一个初始的印象吧:
代理模式的种类主要分为:
远程(Remote)代理
虚拟(Virtual)代理
【How】
还记得上一章状态模式里的糖果机吗?
上一次的解决方案很不错,但还没有彻底的解决问题。
现在糖果公司ceo提出了新的需求:
ceo想要看到库存和糖果机状态的报告,想远程监控糖果机。
远程代理:
客户辅助对象(Client helper)并不真正拥有客户所期望的方法逻辑,它会联系服务器,传送方法调用信息,然后等待服务器返回。
在服务器端,服务辅助对象(Service helper)从客户辅助对象中接收请求(通过Socket连接),将调用的信息解包,然后调用真正服务对象上的方法。
对于服务对象(Service object)来说,调用是本地的,来自服务辅助对象,而不是远程客户。
服务辅助对象得到返回值,将它打包,然后运回到客户辅助对象(通过网络Socket的输出流),客户辅助对象将信息解包,最后将返回值交给客户对象。
RMI的调用过程(这部分讲的挺好):
①
②
③
④
⑤
⑥
------------------------------------------------------------------------------------------------------------------------
RMI 描述
制作远程服务的步骤:
①制作远程接口
1>扩展java.rmi.Remoto
2>声明所有的方法都会抛出RemotoException
3>确定变量和返回值是属于primitive类型或者可序列化Serializable
②制作远程实现
1>实现远程接口
你的服务必须实现远程接口,也就是客户将要调用的方法的接口。
2>扩展UnicastRemoteObject.
3>设计一个不带变量的构造器,并声明RemoteException
4>用RMI Registry注册此服务
③产生Stub和Skeleton
在远程实现类(不是远程接口)上执行rmic
④执行remiregistry
⑤启动服务
服务端的代码
远程接口
// 这个接口扩展自Remote,支持远程调用
public interface MyRemote extends Remote{
// 每次远程调用都是“有风险的”,因为会设计网络和I/O
// 所以这个接口的方法都要声明异常
// 还要确定方法的返回值是primitive类型或者可序列化(Serializable)类型
// 远程方法的变量会被打包并通过网络运送,这要靠序列化来完成
// 自定义的类必须序列化
public String sayHello() throws RemoteException;
}
远程服务(实现)
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{
// 声明默认构造器
protected MyRemoteImpl() throws RemoteException {
super();
}
// 实现接口
public String sayHello() throws RemoteException {
return "Hello, world!";
}
public static void main(String[] args) {
try {
MyRemote service = new MyRemoteImpl();
// 用Naming.rebind()绑定到remiregistry
Naming.rebind("rmi://127.0.0.1/RemoteHello", service);
} catch (Exception e){
e.printStackTrace();
}
}
}
---------------------------------------------------------------------
客户端获取Stub对象
工作方式:
①客户到RMI registry中寻找
Name.lookup("rmi://127.0.0.1/RemoteHello");
②RMI registry返回Stub对象。
③客户调用stub的方法,就像stub就是真正的服务对象一样。
客户端代码:
import java.rmi.*;
public class MyRemoteClient {
public static void main (String[] args) {
new MyRemoteClient().go();
}
public void go() {
try{
// 返回值是Object,所以要转换类型
// 需要ip地址或主机名,以及服务器绑定/重绑定时用的名称
MyRemote service = (MyRemote)Naming.lookup("rmi://127.0.0.1/RemoteHello");
// 看起来和一般的方法调用没有区别!(除了必须注意RemoteException之外)
String s = service.sayHello();
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
}
对于RMI,程序员常犯的三个错误:
***************************华丽的分割线*************************************
ok,我们已经有了RMI的基础知识,现在回到代理模式
GumballMachine套用RMI框架
一、远程代理
GumballMachine服务端
①远程接口
public interface GumballMachineRemote extends Remote {//这个是对RMI有规定意义的,请遵循。
public int getCount() throws RemoteException;//声明的所有方法都要抛出RemoteException
public String getLocation() throws RemoteException;//并且要确定返回或者传入的变量都是基本类型或者可序列化的类型。
public State getState() throws RemoteException;
}
这里我们原来的State就无法序列化,那么我们对它做一些修改:
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
② 制作远程的实现
它首先要继承UnicastRemoteObject类,以成为远程服务,其次它还要实现GumballMachineRemote 这个远程接口的实际功能。
public class GumballMachine
extends UnicastRemoteObject implements GumballMachineRemote
{
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
State state = soldOutState;
int count = 0;
String location;
public GumballMachine(String location, int numberGumballs) throws RemoteException {//设计一个构造方法来抛出异常,因为超类就是这么做的。。
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
winnerState = new WinnerState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
this.location = location;
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}
public void refill(int count) {
this.count = count;
state = noQuarterState;
}
public int getCount() {
return count;
}
public State getState() {
return state;
}
public String getLocation() {
return location;
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public State getWinnerState() {
return winnerState;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("/nMighty Gumball, Inc.");
result.append("/nJava-enabled Standing Gumball Model #2004");
result.append("/nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("/n");
result.append("Machine is " + state + "/n");
return result.toString();
}
}
③在RMI registry中注册
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachineRemote gumballMachine = null;
int count;
if (args.length < 2) {
System.out.println("GumballMachine ");
System.exit(1);
}
try {
count = Integer.parseInt(args[1]);
gumballMachine =
new GumballMachine(args[0], count);
Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);//这句话是关键
} catch (Exception e) {
e.printStackTrace();
}
}
}
首先我们在命令行中启动rmiregistry,然后运行这个类,Server端就开始工作了。
GumballMachine客户端 开始了~~
④开始处理客户端
⑤Ok,万事俱备,只欠东风。来个测试类吧:
public class GumballMonitorTestDrive {
public static void main(String[] args) {
String[] location = {"rmi://santafe.mightygumball.com/gumballmachine",
"rmi://boulder.mightygumball.com/gumballmachine",
"rmi://seattle.mightygumball.com/gumballmachine"};
GumballMonitor[] monitor = new GumballMonitor[location.length];
for (int i=0;i < location.length; i++) {
try {
GumballMachineRemote machine =
(GumballMachineRemote) Naming.lookup(location[i]);
monitor[i] = new GumballMonitor(machine);
System.out.println(monitor[i]);
} catch (Exception e) {
e.printStackTrace();
}
}
for(int i=0; i < monitor.length; i++) {
monitor[i].report();
}
}
}
代理模式 为另一个对象提供一个替身或占位符以控制对这个对象的访问。
- 远程代理控制访问远程对象
- 虚拟代理控制访问创建开销大的资源
二、虚拟代理
虚拟代理作为创建开销大的对象的代表。虚拟代理经常至到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。
显示CD封面的例子
略。
【why】
为什么需要代理模式?
【where & when】
应用的场合?时间?
【在Java世界的应用实例】
Spring 代理模式
【与其他模式的联系 & 区别】
与装饰者模式区别:
装饰模式的主要对象是为对象增加动作,比如 给对象增加 走路像鸭子,会游泳,会 嘎嘎 鸭子叫,使对象看上去就是一直鸭子。
代理模式是代表对象,不是装饰对象。
代表对象,不光是为对象加上动作,而是控制对象的访问,作为真正主题的替身,保护对象避免不必要的访问,
也可以避免在加载大对象的过程中GUI会挂起,或者隐藏主题在远程运行的事实。
【5分钟短剧】
经纪人的故事……
【扩展】
Java API 动态代理
InvocationHandler
*注:这个有空要好好研究下(2016-04-17)
你可以把InvocationHandler想成是代理收到方法调用后,请求做实际工作的对象。
其中只有一个invoke()方法,不管代理被调用的是何种方法,处理器被调用的一定是invoke()方法。
【适用场景】
1、 远程代理:为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
2、 虚拟代理:通过使用过一个小的对象代理一个大对象。这样就可以减少系统的开销。
3、 保护代理:用来控制对真实对象的访问权限。
【一句话总结】
当你看到代理模式四个字的时候,脑海中条件反射,应该会想起这个故事:
高老庄大小姐-悟空-八戒
【鸣谢】
http://blog.csdn.net/shuangde800/article/details/10208847
【设计模式】学习笔记15:代理模式(Proxy Pattern)
【续集】
http://blog.csdn.net/kakaxi_77/article/details/78646115
代理模式2-静态代理与动态代理
最后,老规矩-猴子镇楼: