代理模式定义
为另一个对象提供一个代理或者占位符,来控制对他的访问。
几个控制访问的方法:
-
一个远程代理控制对远程对象的访问
-
虚拟代理控制对创建成本较高的资源的访问
-
保护代理控制对于权限资源的访问
远程代理
见:
设计模式(11)代理模式The Proxy Pattern - 1 - 远程代理rmi
虚拟代理
虚拟代理用来标识一个需要昂贵代价去创建的对象。虚拟代理通常来推迟这个对象的创建,直到这个对象被需要时再创建。在被代理对象创建完成之前,虚拟代理扮演者被代理者(RealSubject)的角色, 创建完成之后,虚拟代理把请求转交给被代理对象。
例如:播放在线音乐,当音乐封面没有被下载的时候,虚拟代理充当封面的角色(可以使一串字符串“正在下载封面”,或者其他的某个默认图片), 一旦封面下载完成,虚拟代理就把所有调用的方法委托给实际的图片。
// Head Fist Design Pattern P456
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();
}
}
}
}
简而言之:
两个类:ImageIcon 、ImageProxy
一个接口:Icon
-
这两个类都实现了Icon的接口。所以外部调用只是用Icon就行。0–
-
ImageProxy内部存在一个ImageIcon类,准确的说ImageProxy负责管理ImageIcon。创建时ImageProxy时新建线程负责下载ImageIcon。没下载完成之前,都是用ImageProx的参数,下载完成后,image非空,就使用image了
保护代理/动态代理
例:有一个PersonImpl,实现了PersonBean接口。包括了一些个人的信息,如姓名、性别、爱好等。
所有的get方法,可以直接调用。
我们对于PersonBean的方法访问有两组不同的权限
-
能够调用setHotOrNotRating()的不能调用其他的setXXX方法
-
能够调用setXXX方法的不能调用setHotOrNotRating()方法
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++;
}
}
接口AND实现类:
-
PersonBean
- PersonImpl:最终要被操作的实体类
-
InvocationHaandler(Java.lang.reflect.*)
- OwnerInvocationHandler:某个实例的拥有者的方法调用管理器,决定某个方法是否有资格被该用户调用
- NonOwnerInvocationHandler:某个实例的非拥有者调用管理器。决定某些方法时候有资格调用。
-
无接口
- MatchMakingTestDrive:测试调用类。在此处动态创建代理。
正常的调用流程:
NonOwnerInvocationHandler和OwnerInvocationHandler相似,所以接下来全部以OwnerInvocationHandler为例
以下代码均来自《Head First Design Patterns》Chapter11
-
MatchMakingTestDrive类:获取代理的实例:
PersonBean getOwnerProxy(PersonBean person) { return (PersonBean) Proxy.newProxyInstance( //通过代理创建一个PersonBean实例 person.getClass().getClassLoader(), person.getClass().getInterfaces(), new OwnerInvocationHandler(person)); // 传入我们写的调用处理。这个决定某些函数能否被调用成功。 }
-
调用上面的函数,我们可以得到一个PersonBean的实例。但是,我们得到的这个实例不包含PersonBean的全部方法,只有OwnerInvocationHandler允许我们调用的方法。调用不被允许的方法会报错。
-
具体OwnerInvocationHandler是如何控制我们对PersonBean方法的使用呢?
public class OwnerInvocationHandler implements InvocationHandler { PersonBean person; /** * 构造的时候要传入用户 */ public OwnerInvocationHandler(PersonBean person) { this.person = person; } /** * invoke接口内部决定什么方法可以被调用 */ public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException { try { // get方法全部被允许 if (method.getName().startsWith(“get”)) { return method.invoke(person, args); // set方法部分允许 } 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; } }
如果是NonOwnerInvocationHandler,那么就允许setHotOrNotRating,而不允许其他的setXXX函数。
总结来说,在调用层,通过动态创建代理,即调用getOwnerProxy()或者getNonOwnerProxy()函数来得到一个"特殊的"PersonBean实例。调用这两个函数虽然得到的都是PersonBean实例,但是他们对函数的访问权限是不同的。这样我们就可以根据实际场合来决定调用哪个函数获取动态代理,从而获得对实例不同的修改权限。