定义:为其他对象提供一种代理以控制对这个对象的访问。
类型:对象结构型模式
类图:
Proxy模式涉及的角色
- 抽象主题角色(Subject):声明了代理主题和真实主题的公共接口,使任何需要真实主题的地方都能用代理主题代替。
- 代理主题角色(Proxy):含有真实主题的引用,从而可以在任何时候操作真实主题。代理主题功过提供和真实主题相同的接口,使它可以随时代替真实主题。代理主题通过持有真实主题的引用,不但可以控制真实主题的创建或删除,还可以在真实主题被调用前进行拦截或在调用后进行某些操作.。
- 真实代理对象(RealSubject):定义了代理角色(Proxy)所代表的具体对象。
为什么我们要控制对象的访问权限呢?其中一个原因是通过控制来延迟对象的创建和实例化(lazy instantiation),直到真正需要使用该对象才进行创建和实例化。由于一些对象创建和实例化需要占用大量系统资源,但我们并不能确定用户一定会调用该对象,所以通过延迟对象实例化来减缓系统资源的消耗。例如文档编辑器如word,我们可以在里面插入链接、图片等,但是并不是我们每次打开word时都有创建和实例化这些对象,特别是实例化图片对象很消耗资源,而且我们有必要实例化所有图片吗?当我们在查看word时,只是看到其中的一部分,所以没有必要实例化所以资源,当我们看下一页时再实例化也不迟。
代码实现
抽象角色(Subject)
interface Subject{
public void request();
}
真实角色(RealSubject)
class RealSubject implements Subject{
@Override
public void request() {
System.out.println("request...");
}
}
代理角色(Proxy)
class ProxySubject implements Subject{
private RealSubject realSubject;// 以真实角色作为代理角色的属性
@Override
public void request() {
preRequest();
if(realSubject==null){
realSubject = new RealSubject();
}
realSubject.request();// 此处执行真实对象的request方法
postRequest();
}
public void preRequest(){ //预处理
System.out.println("before request...");
}
public void postRequest(){ //后处理
System.out.println("after request...");
}
}
客户角色(Client)
public class ProxyClient {
public static void main(String[] args) {
Subject subject = new ProxySubject();
subject.request();
}
}
从上面的例子可以看出代理模式的工作方式。首先,因为代理主题和真实主题都实现了共同的接口。这使我们可以在不改变原来接口的情况下,只要用真实主题对象的地方,都可以用代理主题来代替。其次,代理主题在客户和真实主题之间起了一个中介作用。利用这个中介平台,我们可以在把客户请求传递给真实主题之前(或之后)做一些必要的预处理(AOP)。
Java对代理模式的支持 ---动态代理
上面的代理,我们强迫代理类RedWineProxy实现了抽象接口SellInterface.这导致我们的代理类无法通用于其他接口,所以不得不为每一个接口实现一个代理类.幸好,java为代理模式提供了支持.
Java主要是通过Proxy类和InvocationHandler接口来给实现对代理模式的支持的。
Java代理机制实现代码:
/**
* 代理类一定要实现了InvocationHandler接口
*/
public class ProxyObject implements InvocationHandler {
private Object proxy_obj;
ProxyObject(Object obj) {
this.proxy_obj = obj;
}
public static Object factory(Object obj) {
Class cls = obj.getClass();
// 通过Proxy类的newProxyInstance方法来返回代理对象
return Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(), new ProxyObject(obj));
}
/**
* 实现InvocationHandler接口的invoke
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("函数调用前被拦截了: " + method);
if (args != null) {
// 打印参数列表
System.out.println("方法有 " + args.length + " 个参数");
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
// 利用反射机制动态调用原对象的方法
Object mo = method.invoke(proxy_obj, args);
System.out.println("函数调用后进行处理 : " + method);
return mo;
}
// 测试代码
public static void main(String agr[]) {
ISubject subject = (ISubject) factory(new ProxySubject());
subject.request();
}
}
通过上面的代码可以看出,代理主题ProxyObject类并没有实现我们定义的ISubject接口,而是实现了Java的InvocationHandler接口。这样就把代理主题角色和我们的业务代码分离开来,使代理对象能通用于其他接口。
其实InvocationHandler接口就是一种拦截机制。当系统中有了代理对象以后,对原对象(真实主题)方法的调用都会转由InvocationHandler接口来处理,并把方法信息以参数的形式传递给invoke方法。这样,我们就可以在invoke方法中拦截原对象的调用,并通过反射机制来动态调用原对象的方法。这好象也是spring aop编程的基础吧。
下面实现一个简单的AOP拦截机制
这个例子可以拦截我们指定的函数,并在拦截前后根据需要进行处理
切面接口,定义我们调用前后的处理方法
/**
* 切面接口,通过实现这个接口,我们可以对指定函数在调用前后进行处理
*/
interface AopInterface {
public void before(Object obj);// 调用的处理
public void end(Object obj);// 调用后的处理
}
切面接口实现类,实现处理逻辑
class AopInterfaceImp implements AopInterface {
@Override
public void before(Object obj) {
//add your operation here
System.out.println("调用前拦截");
}
@Override
public void end(Object obj) {
//add your operation here
System.out.println("调用调用后处理");
}
}
代理类实现代码:
class ProxyObject implements InvocationHandler {
private AopInterface aop;// 定义了切入时调用的方法
private Object proxy_obj;
private String methodName;// 指定要切入的方法名
ProxyObject() {
}
public Object factory(Object obj) {
proxy_obj = obj;
Class cls = obj.getClass();
return Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (this.aop == null)
throw new NullPointerException("aop is null");
if (method == null)
throw new NullPointerException("method is null");
Object o;
// 如果指定了要拦截方法名,并且调用的方法和指定的方法名相同,则进行拦截处理
// 否则当正常方法处理
if (methodName != null && method.toString().indexOf(methodName) != -1) {
aop.before(proxy_obj);// 指定方法调用前的处理
o = method.invoke(proxy_obj, args);
aop.end(proxy_obj);// 指定方法调用后的处理
} else {
// 没有指定的方法,以正常方法调用
o = method.invoke(proxy_obj, args);
}
return o;
}
public AopInterface getAop() {
return aop;
}
public void setAop(AopInterface aop) {
this.aop = aop;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
}
目标接口(需要被拦截对象)代码:
// 目标接口(需要被拦截的接口)
interface SubInterface {
public void login(String userName, String password);
public void access(String res);
}
目标接口实现类代码:
class SubInterfaceImpl implements SubInterface {
@Override
public void login(String userName, String password) {
System.out.println("SubInterfaceImpl login(" + userName + ","
+ password + ")");
}
@Override
public void access(String res) {
System.out.println("SubInterfaceImpl access(" + res + ")");
}
}
测试类代码:
public class AOPDemo {
public static void main(String[] args) {
ProxyObject proxy = new ProxyObject();
proxy.setAop(new AopInterfaceImp());// 我们实现的拦截处理对象
proxy.setMethodName("access");// 指定要拦截的函数
SubInterface si = (SubInterface) proxy.factory(new SubInterfaceImpl());
// 因为login方法不是我们指定的拦截函数,AopInterfaceImp是不会被执行
si.login("tt", "12345");
// access是我们指定的拦截方法,所以调用access的前后会先执行AopInterfaceImp
// 对象的两个方法
si.access("authentication");
}
}
通过上面可以看出,拦截机制是代理模式的重要使用方式之一。
除了拦截,代理模式还常用于资源加载。当我们要加载的资源很大时,我们可以让真实主题角色在后台加载资源,让代理主题角色负责处理前台的等待提示信息。
还有就是授权机制。通过代理能拦截真实主题的能力,来控制真实主题的访问权限。
适用场景
在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用 Proxy模式。下面是一些可以使用Proxy模式常见情况:
- 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫做大使(Ambassador)。好处是系统可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的对象是局域的而不是远程的,而代理对象承担了大部份的网络通讯工作。由于客户可能没有意识到会启动一个耗费时间的远程调用,因此客户没有必要的思想准备。
- 虚拟(Virtual)代理(图片延迟加载的例子):根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。使用虚拟代理模式的好处就是代理对象可以在必要的时候才将被代理的对象加载;代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的情况下,虚拟代理的好处就非常明显。
- 保护代理(Protection Proxy):控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
- Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
- 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。