《Head.First设计模式》的学习笔记(15)--代理模式
意图:
为另一个对象提供一个替身或占位符得以访问这个对象。
结构:
接着我们来看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.代理模式也会造成设计中类的数目增加。
上一篇: struts2 ognl 原理
下一篇: OGNL中的#,%,$ 三种符号