定义
Proxy pattern provide a surrogate or placeholder for another object to control access to it.
代理模式为其他对象提供一个(替身)或(占位符)以控制对这个对象的访问。
所谓控制访问,例如:
- 远程代理控制访问远程对象
- 虚拟代理控制访问创建开销大的对象
- 保护代理基于权限控制对资源的访问
类图
1、Subject 抽象主题
为RealSubject和Proxy提供了接口。
通过实现这一接口,Proxy就能在RealSubject出现的地方取代它。
2、RealSubject 具体主题
(被委托角色、被代理角色)。是业务逻辑的具体执行者,是真正做事的对象。
是被proxy代理和控制访问的对象。
3、Proxy 代理主题
(委托类、代理类)。持有RealSubject的引用,负责对RealSubject的应用。
客户和RealSubject的交互都必须通过Proxy,Proxy也控制了对RealSubject的访问。任何用到RealSubject的地方都可以用Proxy取代。
优点
职责清晰:RealSubject实现实际的业务逻辑,不用关心其他非本职的事务
高扩展性:RealSubject是随时都会发生变化的,只要他实现了接口,Proxy即可不做任何修改。
缺点
远程代理
JDK中的RMI机制(Remote Method Invocation,远程方法调用)是一种远程代理的应用。
在java中你不能取得另一个堆的对象引用,也就是说不能这么写代码:
Duke d = <另一个堆的对象>
那如何调用另一个JVM中的对象的方法呢?Java RMI通过远程代理可以办到!下图的Stub就相当于一个Proxy
1. 制作远程接口
- 继承java.rmi.Remote这个记号接口;
- 接口中声明的方法都抛出RemoteException;
- 确保变量和返回值都是基本类型,或可序列化。
public interface MyRemote extends Remote {
public String sayHello() throws RemoteException;
}
——相当于Subject
2. 制作远程实现
- 实现上述接口;
- 扩展UnicastRemoteObject;
- 定义一个无参数构造器,抛出RemoteException;
- 利用RMI Registry注册此服务。
//1.实现上述接口
//2.扩展UnicastRemoteObject
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
//注意不需要声明RemoteException
public String sayHello() {
return "Server says: hey!";
}
//3.定义一个无参数构造器,抛出RemoteException
public MyRemoteImpl() throws RemoteException {}
public static void main(String[] args) {
try {
//4.注册服务
MyRemote service = new MyRemoteImpl();
Naming.rebind("RemoteHello", service);
} catch (Exception e) {
}
}
}
3. 产生Stub和Skeleton
在上述远程实现类MyRemoteImpl上执行rmic命令,生产MyRemoteImpl_Stub.class、MyRemoteImpl_Skel.class两个文件。
4.启动rmiregistry
确保当前目录可以访问上述类,执行rmiregistry命令。否则无法调用Naming.rebind()注册服务。
5.启动服务
java MyRemoteImpl
——当然也可以放到另一个独立的类中启动。
6.客户端代码
现在客户端需要取得Stub对象(代理),以便调用其中的方法
public class Client {
public static void main(String[] args) {
try {
MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
String s = service.sayHello();
...
} catch (Exception e) {
}
}
}
【注】
Java5中甚至连Stub都不需要了。将RMI和动态代理搭配使用,可以动态地产生stub。
虚拟代理
虚拟代理作为创建开销大的那些对象的代表;
虚拟代理经常直到我们真正需要一个对象的时候才创建它;而在创建前和创建中,由虚拟代理来扮演对象的替身;创建后,代理就会将请求直接委托给对象。
【例】
加载网络图片,在加载过程中 显示“downloading”,在加载完成后显示图片:
//Icon类作为Subject
public class ImageProxy implements Icon {
//这个是RealSubject!!!
ImageIcon realIcon;
URL imageURL;
Thread retrievalThread;
boolean retrieving = false;
public ImageProxy(URL url) {imageURL = url}
public int getIconWidth(){
if(realIcon != null){
return realIcon.getIconWidth();
} else {
return 800;
}
}
public void paintIcon(...) {
if(realIcon != null){
//加载完成后,将请求直接委托给对象
realIcon.paintIcon(...);
} else {
//加载过程中,由虚拟代理来扮演对象的替身
print("downloading...");
if(!retrieving){
retrieving = true;
retrievalThread = new Thread(new Runnable() {
public void run(){
//Proxy内部可以创建RealSubject!!
realIcon = new ImageIcon(imageURL);
。。。
}
});
retrievalThread.start();
}
}
}
}
动态代理
动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
其中Proxy由jdk提供,无需编写。我们只要提供InvocationHandler即可。
InvocationHandler的工作是响应代理的任何调用;
可以将其看成是代理收到方法调用后,请求做实际工作的对象。——但是并不是通过调用InvocationHandler的相应方法,而是调用一个方法:invoke。
【例】
世纪佳缘网站的每个用户数据是个PersonBean,要求:允许用户设置自己的个人信息,但不能设置自己的信誉度。
public class PersonBeanImpl implements PersonBean {
String name;
String gender;
int rate;
....getter and setter
}
1. 创建InvocationHandler
分别针对调用本用户方法、调用其他用户方法,编写两个InvocationHandler
//处理调用本用户方法的Handler
public class OwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}
@Override
//每次proxy的方法被调用,都会导致proxy调用invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {
try{
if (method.getName().equals("setRate")){ //不可修改用户自身的信誉度
throw new IllegalAccessException();
} else {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
...
}
return null;
}
}
//处理调用其他用户方法的Handler
public class NoOwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public NoOwnerInvocationHandler(PersonBean person) {
this.person = person;
}
@Override
//每次proxy的方法被调用,都会导致proxy调用invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {
try{
if (method.getName().equals("setName")){//不可修改其他用户的个人信息
throw new IllegalAccessException();
} else {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
...
}
return null;
}
}
2. 创建Proxy类并实例化
创建一个代理,将他的方法调用转发给OwnerInvocationHandler:
/**
* @param loader: the class loader to define the proxy class
* @param interfaces: the list of interfaces for the proxy class to implement
* @param h: the invocation handler to dispatch method invocations to
*/
PersonBean getOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance (
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person)
);
}
这里可以看出,PersonBean是个通用接口,相当于Subject。
3. 客户端代码
PersonBean mike = ...;
//代理
PersonBean ownerProxy = getOwnerProxy(mike);
//访问自身的个人信息
System.out.println(ownerProxy.getName());
mike.setAge(28);
//不能修改自身的信誉度
try {
ownerProxy.setRate(1);
} catch (Exception e) {
System.out.println("ERROR!");
}
//另一个代理
PersonBean noOwnerProxy = getNoOwnerProxy(mike);
//不可修改他人的个人信息
。。。
//可以评价他人的信誉度
。。。
保护代理
是一种根据访问权限决定客户可否访问对象的代理。
如上例,用户无权修改他人的个人信息,就是一种保护代理。
代理模式、装饰者模式
代理模式是控制对象的访问,是代表对象,不是装饰对象。
装饰者则为对象增加行为。