慢啃设计模式:代理模式Proxy
2015-05-08 17:45
519 查看
代理模式也是生活中比较常见的一种模式,比如,现在火车站在城市中各处都会设置代售点,这就是一种代理模式。
代理模式由三种角色构成:
抽象角色。通过接口或抽象类声明真实角色实现的业务方法。所谓抽象角色,实际上就是定义真实中角色所能完成的动作,但不做具体实现。
真实角色。实现抽象角色,实现真实角色所要实现的业务逻辑,供代理角色调用。真实角色需要实现抽象角色中定义的动作。
代理角色。实现抽象角色,是真实角色的代理,通过调用真实角色的业务逻辑方法来实现抽象方法,并附加自己的操作。代理角色可以通过直接调用真实角色的方法实现业务逻辑,自然也可以在此前后添加自己的操作代码。
以火车票售票为例,抽象角色即为售票处,它有售卖火车票的功能;真是角色就是火车站的售票处,它可以完成售票功能;代售点则为代理角色,它最终通过火车站的售票处完成售票功能,但又可以有自己的操作,比如手续费。
![](http://img.blog.csdn.net/20150508174540542?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGFuZzE5ODgwNzIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
首先我们创建一个售票接口,其扮演抽象角色:
接下来创建火车站售票处,这个是真实角色,真的可以买票的地方:
最后,我们可以开设火车票代售点了:
至此,我们就可以选择去火车站买票或者到代售点买票了。现在模拟一下买票:
执行结果:
实际上,上面这种通过实现接口或者继承抽象类的方式实现的代理模式,被称作静态代理模式。静态代理模式的所有代理类都在编译期就已经确定,并生成对应的class文件,并且一个代理类只为一个接口服务。这里举的例子是代购火车票,那如果连付款我们也要代理呢?按照静态代理模式这种思路,首先我们要创建一个付款接口作为抽象角色,然后再建一个银行付款类充当真实角色,那么代理角色可能就是支付宝。
这样下去,我们需要创建多少个代理类呢?
既然有静态代理模式,自然有与之对应的动态代理模式。动态代理模式不需要开发者手工写各种代理类,它的代理类是由Java反射机制在程序运行时动态创建的。
要实现动态代理,我们需要使用JDK中java.lang.reflect包下的Proxy类和InvocationHandler接口,它们提供了动态代理的功能。Proxy的newProxyInstance方法可以创建一个代理对象,它需要三个参数:
ClassLoader loader:第一个参数是类加载器,用于定义代理类。
Class<?>[] interfaces:第二个参数是接口数组,在动态定义代理类的同时,会让代理类实现这些接口。
InvocationHandler h:第三个参数为回调句柄。
InvocationHandler接口下只有一个方法invoke需要重写,代理对象在调用其业务代码时,实际上就是执行这个invoke方法。它也有三个参数:
Object proxy:代理对象。
Method method:需要代理的方法
Object[] args:方法执行传递的参数
仍以前面的火车售票为例,不修改TicketOffice和StationTicketOffice类,来看看动态代理的代理类如何实现:
当然,测试代码也会有一些改动:
执行结果如下:
因为使用了动态代理,所以,我们也可以用这个代理类来处理付款代理了。
无需对代理类做任何修改,更不需要添加新的代理类,测试代码如下:
执行结果:
从前面的代码可以看出,无论是静态代理,还是动态代理,都需要先定义一个接口。这样就会造成普通的类无法对其生成代理类,对我们来说它也是有局限性的。不过好在我们可以使用cglib库来突破这个局限,对普通的java类动态的生成代理类。
cglib是针对类生成代理类的,它的原理是对指定对象的类生成一个子类,并覆盖其中的方法实现代理功能。由于采用的是继承机制,所以如果一个类是final的,cglib是无法对其进行代理的。下面是一个简单的cglib代理对象生成类:
上面是一个较为通用的代理对象生成类,现在我们创建一个类,它不实现任何接口:
再来写一个测试类:
执行结果:
可以看到,Normal类的对象已经被代理了。当我们调用execute时,代理类进行了干预。当然,如果Normal类有多个方法,那么它的任何一个方法在执行时,都会被代理。那么问题来了,如果我恰恰有一个类不希望被代理类干预呢?
cglib还提供了回调过滤器,它的作用就是控制哪个方法被哪个回调干预。现在我们需要修改一下getProxy方法:
给Normal类添加一些其它方法,然后再次在测试类中调用,就会发现,只有调用execute方法时,代理对象才会起作用。
设计模式能够让我们写出更为优雅的代码,如果不使用代理模式,最原始的实现方式恐怕就是去修改真实角色的方法,追加一些操作。但因为代理模式的存在,我们可以不修改原来的类,通过一个代理类,来管理其他类。比如我们要实现日志功能,就可以添加一个日志代理类,将所有的类都代理起来,在这些类的方法执行时,记录下类名、方法名、参数等信息。实际上Spring框架中就使用了代理模式来管理事务,事务开启、回调、提交操作都是代理类完成的,我们只需要关心数据库操作即可。
代理模式的组成
代理模式由三种角色构成:抽象角色。通过接口或抽象类声明真实角色实现的业务方法。所谓抽象角色,实际上就是定义真实中角色所能完成的动作,但不做具体实现。
真实角色。实现抽象角色,实现真实角色所要实现的业务逻辑,供代理角色调用。真实角色需要实现抽象角色中定义的动作。
代理角色。实现抽象角色,是真实角色的代理,通过调用真实角色的业务逻辑方法来实现抽象方法,并附加自己的操作。代理角色可以通过直接调用真实角色的方法实现业务逻辑,自然也可以在此前后添加自己的操作代码。
以火车票售票为例,抽象角色即为售票处,它有售卖火车票的功能;真是角色就是火车站的售票处,它可以完成售票功能;代售点则为代理角色,它最终通过火车站的售票处完成售票功能,但又可以有自己的操作,比如手续费。
代理模式的类图
模拟代售点火车售票
首先我们创建一个售票接口,其扮演抽象角色:package com.proxy.ticket; /** * 售票处 */ public interface TicketOffice { /** * 售票 * @param count 售票张数 */ public void sale(int count); }
接下来创建火车站售票处,这个是真实角色,真的可以买票的地方:
package com.proxy.ticket; /** * 火车站售票处 */ public class StationTicketOffice implements TicketOffice{ /* (non-Javadoc) * @see com.proxy.ticket.TicketOffice#sale(int) */ public void sale(int count) { System.out.println("卖出" + count + "张票。"); } }
最后,我们可以开设火车票代售点了:
package com.proxy.ticket; /** * 火车票代售点 */ public class ProxyStationTicketOffice implements TicketOffice{ // 与真实角色的联系纽带 private TicketOffice office; /** * 构造代售点 * @param office */ public ProxyStationTicketOffice (TicketOffice office){ this.office = office; } /* (non-Javadoc) * @see com.proxy.ticket.TicketOffice#sale(int) */ public void sale(int count) { // 提示不受理退票 System.out.println("代售点不受理退票"); // 售票功能由真实角色完成 this.office.sale(count); // 收取手续费 System.out.println("收取手续费5元"); } }
至此,我们就可以选择去火车站买票或者到代售点买票了。现在模拟一下买票:
package com.proxy.ticket; /** * 代理模式测试类 */ public class TestTicketOffice { public static void main(String[] args) { // 这里是火车站售票处 TicketOffice station = new StationTicketOffice(); // 这是一家代售点 TicketOffice proxy = new ProxyStationTicketOffice (station); // 去火车站买一张票 station.sale(1); // 去代售点买一张票 proxy.sale(1); } }
执行结果:
卖出1张票。 代售点不受理退票 卖出1张票。 收取手续费5元
静态代理模式的局限性
实际上,上面这种通过实现接口或者继承抽象类的方式实现的代理模式,被称作静态代理模式。静态代理模式的所有代理类都在编译期就已经确定,并生成对应的class文件,并且一个代理类只为一个接口服务。这里举的例子是代购火车票,那如果连付款我们也要代理呢?按照静态代理模式这种思路,首先我们要创建一个付款接口作为抽象角色,然后再建一个银行付款类充当真实角色,那么代理角色可能就是支付宝。这样下去,我们需要创建多少个代理类呢?
动态代理模式
既然有静态代理模式,自然有与之对应的动态代理模式。动态代理模式不需要开发者手工写各种代理类,它的代理类是由Java反射机制在程序运行时动态创建的。要实现动态代理,我们需要使用JDK中java.lang.reflect包下的Proxy类和InvocationHandler接口,它们提供了动态代理的功能。Proxy的newProxyInstance方法可以创建一个代理对象,它需要三个参数:
ClassLoader loader:第一个参数是类加载器,用于定义代理类。
Class<?>[] interfaces:第二个参数是接口数组,在动态定义代理类的同时,会让代理类实现这些接口。
InvocationHandler h:第三个参数为回调句柄。
InvocationHandler接口下只有一个方法invoke需要重写,代理对象在调用其业务代码时,实际上就是执行这个invoke方法。它也有三个参数:
Object proxy:代理对象。
Method method:需要代理的方法
Object[] args:方法执行传递的参数
仍以前面的火车售票为例,不修改TicketOffice和StationTicketOffice类,来看看动态代理的代理类如何实现:
package com.proxy.ticket; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 代理工厂 */ public class ProxyFactory implements InvocationHandler{ /** * 保存原始对象(真实角色) */ private Object target; /** * 获取代理对象(代理角色) * @param o * @return */ public Object getProxy(Object o){ this.target = o; Class<?> clazz = o.getClass(); // 创建代理对象实例 return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } /** * 回调处理 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始代理"); // 业务仍由真实角色完成 Object result = method.invoke(target, args); System.out.println("完成代理"); // 返回结果 return result; } }
当然,测试代码也会有一些改动:
package com.proxy.ticket; /** * 测试JDK动态代理 */ public class TestJDKProxy { public static void main(String[] args) { // 创建代理工厂 ProxyFactory factory = new ProxyFactory(); // 这里是火车站售票处 TicketOffice ticket = new StationTicketOffice(); // 获取代理对象 TicketOffice t = (TicketOffice) factory.getProxy(ticket); // 去买两张票 t.sale(2); } }
执行结果如下:
开始代理 卖出2张票。 完成代理
因为使用了动态代理,所以,我们也可以用这个代理类来处理付款代理了。
package com.proxy.ticket; /** * 付款 */ public interface Payment { /** * 支付 * * @param d */ public void pay(double d); }
package com.proxy.ticket; /** * 银行付款 */ public class BankPayment implements Payment{ public void pay(double d) { System.out.println("本次支付" + d + "元"); } }
无需对代理类做任何修改,更不需要添加新的代理类,测试代码如下:
package com.proxy.ticket; /** * 测试JDK动态代理 */ public class TestJDKProxy { public static void main(String[] args) { // 创建代理工厂 ProxyFactory factory = new ProxyFactory(); // 这里是银行付款 Payment pay = new BankPayment(); // 付款代理 Payment payProxy = (Payment) factory.getProxy(pay); // 支付15元 payProxy.pay(15.0); } }
执行结果:
开始代理 本次支付15.0元 完成代理
CGLIB实现动态代理
从前面的代码可以看出,无论是静态代理,还是动态代理,都需要先定义一个接口。这样就会造成普通的类无法对其生成代理类,对我们来说它也是有局限性的。不过好在我们可以使用cglib库来突破这个局限,对普通的java类动态的生成代理类。cglib是针对类生成代理类的,它的原理是对指定对象的类生成一个子类,并覆盖其中的方法实现代理功能。由于采用的是继承机制,所以如果一个类是final的,cglib是无法对其进行代理的。下面是一个简单的cglib代理对象生成类:
package com.cglib.proxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * cglib生成代理类 * */ public class CglibProxy implements MethodInterceptor{ // 原始对象 private Object target; /** * 生成代理对象 * @param o * @return */ public Object getProxy(Object o){ this.target = o; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(o.getClass()); enhancer.setCallback(this); return enhancer.create(); } /** * 代理回调 */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("代理开始"); // 三种方式调用被代理对象的方法 method.invoke(target, args); proxy.invokeSuper(obj, args); proxy.invoke(target, args); System.out.println("代理完成"); return null; } }
上面是一个较为通用的代理对象生成类,现在我们创建一个类,它不实现任何接口:
package com.cglib.proxy; /** * 不实现任何借口的普通类 * */ public class Normal { public void execute(){ System.out.println("我是一个普通类"); } }
再来写一个测试类:
package com.cglib.proxy; public class TestCglibProxy { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); Normal normal = new Normal(); Normal proxyNormal = (Normal) proxy.getProxy(normal); proxyNormal.execute(); } }
执行结果:
代理开始 我是一个普通类 我是一个普通类 我是一个普通类 代理完成
可以看到,Normal类的对象已经被代理了。当我们调用execute时,代理类进行了干预。当然,如果Normal类有多个方法,那么它的任何一个方法在执行时,都会被代理。那么问题来了,如果我恰恰有一个类不希望被代理类干预呢?
cglib还提供了回调过滤器,它的作用就是控制哪个方法被哪个回调干预。现在我们需要修改一下getProxy方法:
/** * 生成代理对象 * @param o * @return */ public Object getProxy(Object o){ this.target = o; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(o.getClass()); // 赋予多个回调,NoOp.INSTANCE是一个没有任何操作的回调 enhancer.setCallbacks(new Callback[]{this, NoOp.INSTANCE}); // 指定回调过滤器 enhancer.setCallbackFilter(new CallbackFilter() { /** * 根据方法名控制当前操作受哪个回调干预 * * @param method 当前执行的方法 * @return 前面设置的回调数组的下标 */ public int accept(Method method) { if("execute".equals(method.getName())){ return 0; } return 1; } }); return enhancer.create(); }
给Normal类添加一些其它方法,然后再次在测试类中调用,就会发现,只有调用execute方法时,代理对象才会起作用。
总结
设计模式能够让我们写出更为优雅的代码,如果不使用代理模式,最原始的实现方式恐怕就是去修改真实角色的方法,追加一些操作。但因为代理模式的存在,我们可以不修改原来的类,通过一个代理类,来管理其他类。比如我们要实现日志功能,就可以添加一个日志代理类,将所有的类都代理起来,在这些类的方法执行时,记录下类名、方法名、参数等信息。实际上Spring框架中就使用了代理模式来管理事务,事务开启、回调、提交操作都是代理类完成的,我们只需要关心数据库操作即可。
相关文章推荐
- 设计模式笔记 – Proxy 代理模式 (Design Pattern)
- Java设计模式Proxy之动态代理
- Java代理设计模式(Proxy)的四种具体实现:静态代理和动态代理
- Java设计模式-----Proxy模式(动态代理)
- 【设计模式】代理模式Proxy---动态代理
- 【白话设计模式一】代理模式(Proxy)
- 设计模式之代理模式(Proxy)
- 设计模式之Proxy(代理)
- Java设计模式:Proxy(代理)
- java设计模式——代理模式(proxy)
- 设计模式一日一练:代理模式(Proxy)
- Java设计模式十八:代理模式(Proxy)
- 【结构型模式】代理模式(Proxy)之23种java设计模式
- 【Unity与23种设计模式】代理模式(Proxy)
- 设计模式学习笔记--Proxy代理模式
- 【设计模式】之 Proxy 代理模式
- [Python设计模式]代理模式(Proxy)
- 设计模式深入学习--Proxy 代理模式(结构型模式)
- 设计模式 代理模式(Proxy)
- 设计模式----代理模式(Proxy)