意图:
为另一个对象提供一个替身或占位符得以访问这个对象。
结构:
接着我们来看RMI远程代理:
1.我们先在服务器注册好几个糖果机,由于我们现在使用RMI,我们需要构造糖果机和状态。
糖果机首先变成一个服务,我们为糖果机创建一个远程接口,让开接口提供了一组可以远程调用的的方法。
public interface GumballMachineRemote extends Remote {
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
接着我们继承这个接口的糖果机
import java.rmi.*;
import java.rmi.server.*;
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();
}
}
其中State要进行传送,所以我们要将其序列化:
import java.io.*;
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
由于状态有糖果机的引用我们不希望糖果机被传送,我们只要将其非序列化,如下:
public class NoQuarterState implements State {
transient GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}
public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}
public void dispense() {
System.out.println("You need to pay first");
}
public String toString() {
return "waiting for quarter";
}
}
加上transient,就可以避免序列化。
最后我们将其注册到RMI registry中:
package headfirst.proxy.gumball;
import java.rmi.*;
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachineRemote gumballMachine = null;
int count;
if (args.length < 2) {
System.out.println("GumballMachine <name> <inventory>");
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();
}
}
}
接着我们来看客户端,也就是监控机:
import java.rmi.*;
public class GumballMonitor {
GumballMachineRemote machine;
public GumballMonitor(GumballMachineRemote machine) {
this.machine = machine;
}
public void report() {
try {
System.out.println("Gumball Machine: " + machine.getLocation());
System.out.println("Current inventory: " + machine.getCount() + " gumballs");
System.out.println("Current state: " + machine.getState());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
接着我们在调用客户端的时候实现代理,然后用代理调用远程服务端的方法,代码如下:
import java.rmi.*;
public class GumballMonitorTestDrive {
public static void main(String[] args) {
//这些是代表服务端启用了3个这样地址的RMI服务端
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++) {
//report方法调用了代理的远程方法,相当于调用服务端的方法
monitor[i].report();
}
}
}
上述就是一个远程代理的,远程代理是可以作为另一个JVM上对象的本地代表。
接着我们将介绍虚拟代理,虚拟代理作为创建开销大的对象的代表。虚拟对象经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中,有虚拟对象来扮演对象的替身,对象创建后,代理就会将请求直接委托给对象。
例子:
我们经常会碰到JFrame加载一个大的网络图片,这时候我们就可以先使用代理显示正在加载图片,等图片真正加载好我们才“画”上这个图片,先看类图:
Icon是使用Swing的Icon接口,在用户界面上显示图片。
接着我们来实现ImageIcon继承了Icon接口
package headfirst.proxy.virtualproxy;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class ImageProxy implements Icon {
ImageIcon imageIcon;
URL imageURL;
Thread retrievalThread;
boolean retrieving = false;
public ImageProxy(URL url) { imageURL = url; }
public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}
public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}
public void paintIcon(final 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+190);
if (!retrieving) {
retrieving = true;
retrievalThread = new Thread(new Runnable() {
public void run() {
try {
imageIcon = new ImageIcon(imageURL, "CD Cover");
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
}
我们先接着看
import java.awt.*;
import javax.swing.*;
class ImageComponent extends JComponent {
private Icon icon;
public ImageComponent(Icon icon) {
this.icon = icon;
}
public void setIcon(Icon icon) {
this.icon = icon;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
int x = (800 - w)/2;
int y = (600 - h)/2;
icon.paintIcon(this, g, x, y);
}
}
最后我们来看主程序其中调用的代码
Icon icon = new ImageProxy(initialURL);
imageComponent = new ImageComponent(icon);
frame.getContentPane().add(imageComponent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800,600);
frame.setVisible(true);
现在我们来看看这虚拟代理到底是如何运行的。
1.我们先初始化了一个代理,只是将远程url串构造到代理中。
2.然后我们将icon这个代理委托到ImageComponent中。
3.到上述为止,我们依然没有看到任何代理的影子,接着就是见证神奇的一面。接着我们调用添加到JFrame窗口,由于调用这个方法,会先调用ImageComponent中的paintComponent,接着我们依次调用 icon.getIconWidth()和icon.getIconHeight(),最重要我们调用代理中的icon.paintIcon(this, g, x, y);
在上述方法调用过程中个,代理中的ImageIcon对象均为null,这个if-else就很明显,这边在详解一下paintIcon()方法,由于第一次调用imageIcon为空,然后我们创建一个线程类,然后将这个线程类启动,这个线程类到底做了什么呢?看下面代码:
imageIcon = new ImageIcon(imageURL, "CD Cover");
c.repaint();
这边等待imageIcon加载成功后,重新刷新ImageCompontent方法。最后显示图片。
最后我们将介绍一个代理,利用JAVA-API实现的一个动态代理,这边我们将实现保护代理。类图与普通的代理有点不同
代理变成两个类。然后让我们看一下具体的例子,我们希望去保护一个人的具体信息,这些信息只有本人能够进行修改,而评价只有非本人进行修改。
首先我们先实现一个接口:
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);
}
接着我们要实现两个InvocationHandler,一个是拥有者,另一个是非拥有者的:
package headfirst.proxy.javaproxy;
import java.lang.reflect.*;
public class OwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {
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;
}
}
package headfirst.proxy.javaproxy;
import java.lang.reflect.*;
public class NonOwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {
try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
接着我们将看到我们如何创建两个代理
package headfirst.proxy.javaproxy;
import java.lang.reflect.*;
import java.util.*;
public class MatchMakingTestDrive {
Hashtable datingDB = new Hashtable();
public static void main(String[] args) {
MatchMakingTestDrive test = new MatchMakingTestDrive();
test.drive();
}
public MatchMakingTestDrive() {
initializeDatabase();
}
public void drive() {
//从数据库中取出一个数据
PersonBean joe = getPersonFromDatabase("Joe Javabean");
//创建一个拥有者对象
PersonBean ownerProxy = getOwnerProxy(joe);
//调用getter方法
System.out.println("Name is " + ownerProxy.getName());
//调用setter方法
ownerProxy.setInterests("bowling, Go");
System.out.println("Interests set from owner proxy");
try {
//试着调用评价方法,会抛出异常
ownerProxy.setHotOrNotRating(10);
} catch (Exception e) {
System.out.println("Can't set rating from owner proxy");
}
System.out.println("Rating is " + ownerProxy.getHotOrNotRating());
//创建一个非拥有者对象
PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
//
System.out.println("Name is " + nonOwnerProxy.getName());
try {
//调用setter方法,会抛出异常
nonOwnerProxy.setInterests("bowling, Go");
} catch (Exception e) {
System.out.println("Can't set interests from non owner proxy");
}
//调用评价方法,没有问题
nonOwnerProxy.setHotOrNotRating(3);
System.out.println("Rating set from non owner proxy");
System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating());
}
PersonBean getOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}
PersonBean getNonOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}
PersonBean getPersonFromDatabase(String name) {
return (PersonBean)datingDB.get(name);
}
void initializeDatabase() {
PersonBean joe = new PersonBeanImpl();
joe.setName("Joe Javabean");
joe.setInterests("cars, computers, music");
joe.setHotOrNotRating(7);
datingDB.put(joe.getName(), joe);
PersonBean kelly = new PersonBeanImpl();
kelly.setName("Kelly Klosure");
kelly.setInterests("ebay, movies, music");
kelly.setHotOrNotRating(6);
datingDB.put(kelly.getName(), kelly);
}
}
总结:
1.代理模式还有很多变种,例如缓存代理,同步代理,防火墙代理,和写入时复制代理
2.代理在结构上类似装饰者,但是目的不同哦,装饰者为对象加上行为,而代理是控制行为。
3.代理模式是要实现接口,而适配器是要改变接口的实现。
4.代理模式也会造成设计中类的数目增加。