定义:为其他对象提供一种代理以控制对这个对象的访问。
类型:对象结构型模式
类图:
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..."); } }
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..."); } }
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(); } }
下面实现一个简单的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模式常见情况: