代理模式可以分为以下四类
- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
1 远程代理设计模式
1.1 应用场景
为一个对象在不同地址空间提供局部代表这样可以隐藏一个对象存在于不同地址空间的事实,例如:老阮(MrRuan)在地点A,老李在地点B,餐厅柜台也在地点乙,那么老李和老软住在一起(都在地点甲住),那么老李就是餐厅(地点B)在老软与老李住处(地点A)的代表。
1.2 概念
代理设计模式为另一个对象提供一个替身或者占位符用以控制对这个对象的访问。使用代理模式创建代表(representative)对象,让代表对象控制某个对象的访问,被代理的对象可以是远程的对象、创建开销很大的对象或者是需要安全控制的对象。
1.3 Class Diagram
首先是Subject,它是RealSubject和Proxy提供了相同的接口。通过实现同一个接口,Proxy在RealSubject出现的地方取代它。RealSubject才是真正做事的对象,它是被proxy代理和控制访问的对象。Proxy持有RealSubject的引用,因为Proxy和RealSubject实现相同的接口,所以任何用到RealSubject的地方,都可以用Proxy取代。Proxy也控制了对RealSubject的访问,在某些情况下,我们可能需要这样的控制。这些情况包括RealSubject是远程的对象,RealSubject创建开销大或者RealSubject需要被保护。
1.4 Implementation
下面展示一个远程代理设计模式的实现:
1. 远程代理类接口Subject
public interface GumballMachineRemote extends Remote {
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
2. RealSubject接口正在的实现类
public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote {
private State soldOutState;
private State noQuarterState;
private State hasQuarterState;
private State soldState;
private State winnerState;
private State state;
private int count = 0;
private 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;
this.location=location;
if (numberGumballs > 0) {
state = noQuarterState;
} else {
state = soldOutState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}
public int getCount() {
return count;
}
void refill(int count) {
this.count += count;
System.out.println("The gumball machine was just refilled; it's new count is: " + this.count);
state.refill();
}
void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
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 getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
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();
}
}
3. 通过在“classes”目录下执行:rmic com.basic.proxypattern.gumball.GumballMachine 命令生成客户端辅助类Stub和服务器辅助类Skeleton类。
4. 通过在“classes”目录下执行:rmiregistry 启动RMI Registry服务
5. 将我们的GumballMachineRemote注册到RMI Registry服务中去
public class GumballMachineRemoteTestDrive {
public static void main(String[] args) throws RemoteException {
GumballMachineRemote gumballMachineRemote=null;
int count=0;
if (args.length > 2 ){
System.out.println("GumballMachine <name> <inventory>");
System.exit(0);
}
try {
count=Integer.parseInt(args[1]);
gumballMachineRemote=new GumballMachine(args[0],count);
Naming.rebind("//"+args[0]+"/gumballmachine",gumballMachineRemote);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
6. 这个时候就可以在客户端调用RMI Reigistry注册过的服务了
public class GumballMointorRemoteTestDrive {
public static void main(String[] args){
try {
GumballMachineRemote machine= (GumballMachineRemote) Naming.lookup("rmi://localhost/gumballmachine");
GumballMonitor gumballMonitor=new GumballMonitor(machine);
gumballMonitor.report();
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
通过调用代理的方法,远程调用可以跨过网络,返回字符串、整数和State对象。因为我们使用的是代理,调用的方法会在远程执行。
- CEO执行监视器,先取得远程糖果机的代理,然后调用每个代理的getState()、getCount()和getLocation()方法。
- 代理上的getState被调用,此调用转发到远程服务上。远程服务器辅助类Skeleton接收到请求,然后转发给糖果机。
- 糖果机将状态返回给Skeleton,skeleton将状态序列化,通过网络传回给代理,代理将其反序列化,把它当做一个对象返回给监视器。
2. 虚拟代理设计模式
2.1 应用场景
是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真是对象,例如:老阮(MrRuan)在地点A,到餐厅柜台(地点B),因为距离远却是很费劲,而老李刚好在这里(地点B)上班,所以让老李去办是很可行的办法。
2.2 概念
虚拟代理作为创建开销大的对象的代表。虚拟代理经常知道我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建之后,代理就会将请求直接委托给对象
2.3 Class Diagram
这是一个根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问。
2.4 Implementation
public class ImageProxy implements Icon {
private ImageIcon imageIcon;
private URL url;
private Thread retrievalThread;
private boolean retrieving = false;
public ImageProxy(URL url) {
this.url = url;
}
@Override
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 , pleas wait...",x+300,y+190);
if(!retrieving){
retrieving=true;
retrievalThread=new Thread(new Runnable() {
@Override
public void run() {
try {
imageIcon=new ImageIcon(url, "CD cover");
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
@Override
public int getIconWidth() {
if(imageIcon != null){
return imageIcon.getIconWidth();
}else
return 800;
}
@Override
public int getIconHeight() {
if(imageIcon != null){
return imageIcon.getIconHeight();
}else
return 600;
}
}
class ImageComponent extends JComponent {
private static final long serialVersionUID = 1L;
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);
}
}
public class ImageProxyTestDrive {
ImageComponent imageComponent;
JFrame frame = new JFrame("CD Cover Viewer");
JMenuBar menuBar;
JMenu menu;
Hashtable<String, String> cds = new Hashtable<String, String>();
public static void main (String[] args) throws Exception {
ImageProxyTestDrive testDrive = new ImageProxyTestDrive();
}
public ImageProxyTestDrive() throws Exception{
cds.put("Buddha Bar","http://images.amazon.com/images/P/B00009XBYK.01.LZZZZZZZ.jpg");
cds.put("Ima","http://images.amazon.com/images/P/B000005IRM.01.LZZZZZZZ.jpg");
cds.put("Karma","http://images.amazon.com/images/P/B000005DCB.01.LZZZZZZZ.gif");
cds.put("MCMXC A.D.","http://images.amazon.com/images/P/B000002URV.01.LZZZZZZZ.jpg");
cds.put("Northern Exposure","http://images.amazon.com/images/P/B000003SFN.01.LZZZZZZZ.jpg");
cds.put("Selected Ambient Works, Vol. 2","http://images.amazon.com/images/P/B000002MNZ.01.LZZZZZZZ.jpg");
URL initialURL = new URL((String)cds.get("Selected Ambient Works, Vol. 2"));
menuBar = new JMenuBar();
menu = new JMenu("Favorite CDs");
menuBar.add(menu);
frame.setJMenuBar(menuBar);
for (Enumeration<String> e = cds.keys(); e.hasMoreElements();) {
String name = (String)e.nextElement();
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(event -> {
imageComponent.setIcon(new ImageProxy(getCDUrl(event.getActionCommand())));
frame.repaint();
});
}
// set up frame and menus
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);
}
URL getCDUrl(String name) {
try {
return new URL((String)cds.get(name));
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
3. 保护代理设计模式
3.1 应用场景
保护代理模式就是一种可以根据访问权限决定客户可否访问对象的代理。比如,如果你有一个雇员对象,保护代理允许雇员调用对象上的某些方法,经理还可以多调用一些其他的方法(像setSlary()),而人力资源的雇员可以调用对象上的所有方法。
3.2 概念
按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。保护代理模式可以根据访问权限来决定客户是否可以访问对象的代理。
3.3 Class Diagram
Java在java.lang.reflect包中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或者多个接口,并将方法的调用转发到你指定的类。因为实际的代理类是在运行时创建的,我们称这个Java技术为动态代理。
InvocationHandler的工作是响应代理的任何调用。你可以把InvocationHandler想成是代理收到方法调用后,请求实际工作的对象。
3.4 Implementation
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 {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getInterests() {
return interests;
}
public int getHotOrNotRating() {
if (ratingCount == 0) return 0;
return (rating/ratingCount);
}
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setInterests(String interests) {
this.interests = interests;
}
public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}
public class NonOwnerInvocationHandler implements InvocationHandler{
private PersonBean personBean;
public NonOwnerInvocationHandler(PersonBean personBean) {
this.personBean = personBean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(method.getName().startsWith("get")){
throw new IllegalAccessException();
}else if(method.getName().startsWith("set")){
throw new IllegalAccessException();
}else if(method.getName().startsWith("setHotOrNotRating")){
return method.invoke(personBean,args);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
public class OwnerInvocationHandler implements InvocationHandler{
private PersonBean personBean;
public OwnerInvocationHandler(PersonBean personBean) {
this.personBean = personBean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(method.getName().startsWith("get")){
return method.invoke(personBean,args);
}else if(method.getName().startsWith("set")){
return method.invoke(personBean,args);
}else if(method.getName().startsWith("setHotOrNotRating")){
throw new IllegalAccessException();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
public class MatchMakingTestDrive {
HashMap<String, PersonBean> datingDB = new HashMap<String, PersonBean>();
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);
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 = 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());
}
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);
}
}
4. 其他代理设计模式
- 防火墙代理(Firewall Proxy):控制网络资源的访问,保护主题免于“坏客户”的侵害。
- 智能引用代理(Smart Reference Proxy):当主题被引用时,进行额外的动作,例如计算一个对象被引用的次数。
- 缓存代理(Caching Proxy):为开销大的运算结果提供暂时的存储:它也允许多个客户共享结果,以减少计算或者网络延迟。
- 同步代理(Synchronized Proxy):在多线程的情况下为主题提供安全的访问。
- 复杂隐藏代理(Complexity Hiding Proxy):用来隐藏一个类的复杂集合的复杂度,并进行访问控制。
- 写入复制代理(Copy-On-Write Proxy):用来控制对象的复制,方法是延迟对象的复制,直到客户真正需要为止。
5. JDK
- java.lang.reflect.Proxy
- RMI