代理模式指的是一个可以用来控制对某个对象访问的模式,其地位类似于现实世界中的经纪人,我们每次对重要对象的调用都需要经过经纪人的处理才能进行,如果遇到不合法或者不安全的访问,经纪人就会对其进行拒绝。代理模式主要有三种表现形式:远程代理,虚拟代理和安全代理。
远程代理指的是Java RMI访问,服务端将需要暴露的对象注册于RMI registry中,但是通常需要暴露的对象中不仅仅包含需要暴露的信息,还包含很多额外的信息,因而不能直接将其暴露给客户端,而是使用一个代理,客户端调用该代理的方法,代理会将方法的调用转接给真正执行的对象,客户端的调用感觉就像是直接调用服务端的方法一样。远程代理服务端需要将代理的对象继承UnicastRemoteObject,并且继承一个代理接口。这里需要注意的有两点:①需要代理的对象中的重要属性需要使用transient来描述,以防止其被客户端获取;②需要代理的对象需要实现Serializable接口,以供远程对象传输,不过UnicastRemoteObject已经实现了Serializable接口,因而代理对象不需要显示实现该接口。其次,服务端需要将被代理对象注册到RMI registry中,以供客户端访问。而客户端则需要下载服务端的代理接口,通过该接口客户端可以进行其所需的操作,并且客户端需要在RMI registry中查找远程传输过来的对象,将其强转为代理对象类型。以下是远程代理的示例代码:
被代理对象:
public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote { private State soldOutState; private State noQuarterState; private State hasQuarterState; private State soldState; private State winnerState; private String location; private State state; private int count = 0; 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.location = location; this.count = numberGumballs; if (numberGumballs > 0) { state = noQuarterState; } } public void insertQuarter() { state.insertQuarter(); } public void ejectQuarter() { state.ejectQuarter(); } public void turnCrank() { state.turnCrank(); state.dispense(); } public void setState(State state) { this.state = state; } public void releaseBall() { System.out.println("A gumball comes rolling out the slot..."); if (count != 0) { count--; } } 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 State getState() { return state; } public int getCount() { return count; }}
服务端RMI注册类:
public class GumballMachineTestDrive { public static void main(String[] args) { int count = 100; if (args.length < 2) { System.out.println("GumballMachine"); System.exit(1); } try { GumballMachine gumballMachine = new GumballMachine("seattle.mightygumball.com", count); Naming.rebind("//seattle.mightygumball.com/gumballmachine", gumballMachine); } catch (RemoteException | MalformedURLException e) { e.printStackTrace(); } }}
代理接口:
public interface GumballMachineRemote extends Remote { int getCount() throws RemoteException; String getLocation() throws RemoteException; State getState() throws RemoteException;}
客户端驱动类:
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[] monitors = new GumballMonitor[location.length]; try { for (int i = 0; i < location.length; i++) { GumballMachineRemote machine = (GumballMachineRemote) Naming.lookup(location[i]); monitors[i] = new GumballMonitor(machine); System.out.println(monitors[i]); } } catch (NotBoundException | MalformedURLException | RemoteException e) { e.printStackTrace(); } for (int i = 0; i < monitors.length; i++) { monitors[i].report(); } }}
客户端管理类:
public class GumballMonitor { private 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(); } }}
虚拟代理指的是对一些创建消耗比较大或者比较耗时的对象的保护。比如在GUI界面中,图片的加载受到很多因素的影响,如果在加载图片的时候将客户端挂起的话会非常影响用户体验,因而在图片的加载就可以使用代理来进行,代理对象在图片还没有加载出来的时候将一个加载中的低耗图标返回给客户端,当图片加载完成之后代理对象就将真正的图片返回给客户。类似这样使用虚拟代理的场景还有使用时复制等。以下是使用虚拟代理加载图片的示例代码:
public class ImageProxy implements Icon { private ImageIcon imageIcon; private URL imageUrl; private boolean retrieving; private State state; public ImageProxy(URL imageUrl) { this.imageUrl = imageUrl; this.state = new ImageNotLoaded(); } @Override public void paintIcon(Component c, Graphics g, int x, int y) { if (!retrieving) { retrieving = true; new Thread(() -> { try { imageIcon = new ImageIcon(imageUrl, "CD Cover"); state = new ImageLoaded(imageIcon); c.repaint(); } catch (Exception e) { e.printStackTrace(); } }).start(); } state.printIcon(c, g, x, y); } @Override public int getIconWidth() { return state.getIconWidth(); } @Override public int getIconHeight() { return state.getIconHeight(); }}
标志当前处于什么状态(加载中,加载完成)的接口:
public interface State { int getIconWidth(); int getIconHeight(); void printIcon(Component c, Graphics g, int x, int y);}
加载中状态:
public class ImageLoaded implements State { private ImageIcon imageIcon; public ImageLoaded(ImageIcon imageIcon) { this.imageIcon = imageIcon; } @Override public int getIconWidth() { return imageIcon.getIconWidth(); } @Override public int getIconHeight() { return imageIcon.getIconHeight(); } @Override public void printIcon(Component c, Graphics g, int x, int y) { imageIcon.paintIcon(c, g, x, y); }}
加载完成状态:
public class ImageNotLoaded implements State { private static final int DEFAULT_WIDTH = 800; private static final int DEFAULT_HEIGHT = 600; @Override public int getIconWidth() { return DEFAULT_WIDTH; } @Override public int getIconHeight() { return DEFAULT_HEIGHT; } @Override public void printIcon(Component c, Graphics g, int x, int y) { g.drawString("Loading CD cover, please wait...", x + 300, y + 190); }}
这里使用状态模式来标志当前是处于加载中状态还是加载完成状态。
安全代理指的是使用代理模式来控制对象的访问,比如对某个对象的访问权限控制。在java中已经对这种代理模式提供了内置支持,其主要类是Proxy,其有如下静态方法用于创建代理对象:
public static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler h) throws IllegalArgumentException;
该方法第一个参数是被代理对象的类加载器,第二个对象是被代理对象实现的接口数组,第三个对象则是被代理对象的一个调用处理器。其是一个接口,该接口有如下主要方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
每次调用代理对象的方法时都会先调用该方法,通过该方法调用被代理对象的方法,因而在该方法中可以加入被代理对象的访问权限控制。该方法第一个参数是代理对象的实例,第二个参数是当前调用的被代理对象中的方法,第三个参数是调用时传入的参数列表。以下是使用安全代理的一个示例:
被代理对象接口:
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);}
被代理对象:
public class PersonBeanImpl implements PersonBean { private String name; private String gender; private String interests; private int rating; private int ratingCount = 0; @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public String getGender() { return gender; } @Override public void setGender(String gender) { this.gender = gender; } @Override public String getInterests() { return interests; } @Override public int getHotOrNotRating() { return ratingCount == 0 ? 0 : rating / ratingCount; } @Override public void setInterests(String interests) { this.interests = interests; } @Override public void setHotOrNotRating(int rating) { this.rating += rating; ratingCount++; }}
代理工厂:
public final class ProxyFactory { public static PersonBean getOwnerProxy(PersonBean person) { return getProxy(person, new OwnerInvocationHandler(person)); } public static PersonBean getNonOwnerProxy(PersonBean person) { return getProxy(person, new NonOwnerInvocationHandler(person)); } private static PersonBean getProxy(PersonBean person, InvocationHandler invocationHandler) { return (PersonBean) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), invocationHandler); }}
代理处理器:
public class NonOwnerInvocationHandler implements InvocationHandler { private PersonBean person; public NonOwnerInvocationHandler(PersonBean person) { this.person = person; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException { try { if (method.getName().matches("^((get.+)|(setHotOrNotRating))$")) { return method.invoke(person, args); } else { throw new IllegalAccessException(); } } catch (InvocationTargetException e) { e.printStackTrace(); } return null; }}
public class OwnerInvocationHandler implements InvocationHandler { private PersonBean person; public OwnerInvocationHandler(PersonBean person) { this.person = person; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException { try { if (method.getName().matches("^((get)|(set)).+")) { return method.invoke(person, args); } else if (method.getName().equals("setHotOrNotRating")) { throw new IllegalAccessException("cannot invoke PersonBean.setHotOrNotRating method"); } } catch (InvocationTargetException e) { e.printStackTrace(); } return null; }}
测试驱动类:
public class MatchMarketingTestDrive { public static void main(String[] args) { new MatchMarketingTestDrive().drive(); } public void drive() { PersonBean joe = getPersonFromDatabase("Joe Javabean"); PersonBean ownerProxy = ProxyFactory.getOwnerProxy(joe); System.out.println("Name is " + ownerProxy.getName()); 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 = ProxyFactory.getNonOwnerProxy(joe); System.out.println("Name is " + nonOwnerProxy.getName()); try { 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()); } private PersonBean getPersonFromDatabase(String name) { PersonBean person = new PersonBeanImpl(); person.setName(name); return person; }}