一、代理模式的定义
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介,代理模式也叫做委托模式。
二、为什么使用代理模式
- 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
三、代理模式优缺点
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
代理模式的主要缺点是:
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
四、代理模式的结构与实现
代理的实现是有多种方式的,常见就是静态代理、动态代理(JDK动态代理、CGLIB动态代理),因此接下来一一讲解这三种实现方式。
1、静态代理
静态代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问。代理模式的主要角色如下:
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
其结构图如图所示。
代码如下:
public class ProxyTest { public static void main(String[] args) { Proxy proxy=new Proxy(); proxy.Request(); } } //抽象主题 interface Subject { void Request(); } //真实主题 class RealSubject implements Subject { public void Request() { System.out.println("访问真实主题方法..."); } } //代理 class Proxy implements Subject { private RealSubject realSubject; public void Request() { if (realSubject==null) { realSubject=new RealSubject(); } preRequest(); realSubject.Request(); postRequest(); } public void preRequest() { System.out.println("访问真实主题之前的预处理。"); } public void postRequest() { System.out.println("访问真实主题之后的后续处理。"); } }
输出结果为:
访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。
静态代理优缺点
- 优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展。
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护。
2、动态代理(JDK动态代理)
静态代理会手动创建很多代理类的问题,动态代理就解决了这个问题,其中一种就是JDK自带动态代理。其通过自己实现InvocationHandler来实现动态代理,真正的代理对象由JDK再运行时为我们动态的来创建。其结构图如下:
本例展示一个车启动的例子,其代码实现如下:
//目标类接口 interface Car{ void run(); } //目标类 class Benz implements Car{ @Override public void run() { System.out.println("奔驰车跑起来了"); } } class CarUtils{ public static void methodBefore() { System.out.println("跑之前要点火 ... ..."); } public static void methodAfter() { System.out.println("跑起来后会换挡 ... ..."); } } class MyInvocationHandle implements InvocationHandler{ private Object target; public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { CarUtils.methodBefore(); method.invoke(target, args); CarUtils.methodAfter(); return null; } } //生产代理对象的工厂 class MyProxyFactory{ public static Object getProxy(Object target) { MyInvocationHandle handle = new MyInvocationHandle(); handle.setTarget(target); Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handle); return proxy; } } public class ProxyTest { public static void main(String[] args) { Car car = new Benz(); Car proxy =(Car) MyProxyFactory.getProxy(car); proxy.run(); } }
注意Proxy.newProxyInstance()方法接受三个参数:
ClassLoader loader
: 指定当前目标对象使用的类加载器,获取加载器的方法是固定的Class<?>[] interfaces
: 指定目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler:
指定
动态处理器,
执行目标对象的方法时,会触发事件处理器的方法
JDK动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是JDK自带动态代理只能支持实现了Interface的类。
3、动态代理(CGLIB动态代理)
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLIB了。CGLIB采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。
我们通过实现MethodInterceptor来实现CGLIB动态代理,CGLIB动态代理类图关系如下图所示:
代码如下:
class ArraySort{ public void quickSort(int[] arr) { Arrays.sort(arr); } public void selectSort(int[] arr) { for (int i = 0; i < arr.length; i++) { for (int j = i+1; j < arr.length; j++) { if (arr[i] > arr[j]) { int temp = 0; temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } } public void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = 0; temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } } class CglibInteceptor implements MethodInterceptor { private Object object; public CglibInteceptor(Object object) { this.object = object; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("CGLIB动态代理执行前"); Object result = method.invoke(object,objects); System.out.println("CGLIB动态代理执行后"); return result; } public Object getProxy(){ Enhancer enhancer = new Enhancer(); enhancer.setCallback(this); enhancer.setSuperclass(object.getClass()); return enhancer.create(); } }
public class CGLibProxyTest { public static void main(String[] args) { int[] arr = new int[100000]; for (int i = 0; i < arr.length; i++) { arr[i] = (int) (Math.random() * 1000); } ArraySort arraySort = new ArraySort(); arraySort = (ArraySort) new CglibInteceptor(arraySort).getProxy(); arraySort.bubbleSort(arr); arraySort.selectSort(arr); arraySort.quickSort(arr); } }
CGLIB代理总结: CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
以上就是三种代理具体实现。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。在Spring的AOP编程中: 如果加入容器的目标对象有实现接口,用JDK代理;如果目标对象没有实现接口,用Cglib代理。