代理模式
代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问。
那么为什么要使用代理模式呢?
1、隔离,客户端类不能或者不想直接访问目标对象,代理类可以在远程客户端类和目标类之间充当中介。
2.代理类可以对业务或者一些消息进行预处理,做一些过滤,然后再将消息转给目标类,主要处理逻辑还是在目标类,符合开闭原则。
在我们生活中有很多体现代理模式的例子,如中介、媒婆、经纪人等等,比如说某个球队要签约一个球星,就需要和经纪人进行沟通,在一些编程框架中,也有很多地方使用代理模式,如Spring的AOP,java的RMI远程调用框架等。
代理模式分为静态代理和动态代理,动态代理又分为jdk动态代理和cglib动态代理,下面分别来阐述下这几种代理模式的区别。
静态代理
静态代理在使用的时候,需要定义一个接口或者父类,代理类和目标类(被代理类)都需要实现这个接口,代理类持有目标类的引用。我们以球员签约为例,湖人想要签下安东尼戴维斯,安东尼说,先和我的经纪人商讨签约情况,商谈成功之后再来找我签约。我们定义一个会谈的接口,这个接口提供一个签约的方法,再定义一个经纪人类和球员类,分别实现会谈接口的签约方法。经纪人和湖人说,想要签下戴维斯也可以,不过我们需要交易否决权,且最后一年是球员选项。湖人的魔术师想了想,詹姆斯巅峰期的尾巴也没几年了,反正这几年垃圾合同也签了不少,不在乎这一个,而且戴维斯正值巅峰,联盟前十球员,不算太亏,就同意了,毕竟还是总冠军重要。所以,经纪人在正式签约前,谈妥了戴维斯的球员选项和薪水,剩下的就需要戴维斯自己亲自签约了。然后经纪人就拉着戴维斯来签约了。(写博客的期间,魔术师辞职了,我........,算了,懒得改了)
/** * 会谈接口,有一个签约的方法 */ public interface Talk { public void sign(); } /** * 球员安东尼戴维斯,实现了签约的方法,需要本人亲自签约 */ public class Davis implements Talk { @Override public void sign() { System.out.println("签约了,5年2.25亿美元"); } } /** * 经纪人,也实现了签约的方法,持有球员的引用,但是具体签约流程还是必须由球员完成 */ public class Broker implements Talk { private Davis davis; public Broker(Davis davis){ this.davis = davis; } @Override public void sign() { System.out.println("我们拥有最后一年的球员选项"); davis.sign(); System.out.println("签约成功,交易否决权开始生效"); } } //** * 测试类,只需要调用经纪人的签约方法就可以了 */ public class StaticProxyTest { public static void main(String[] args) { Davis davis = new Davis(); Broker broker = new Broker(davis); broker.sign(); } } 控制台打印: 我们拥有最后一年的球员选项 签约了,5年2.25亿美元 签约成功,交易否决权开始生效
通过以上代码,我们发现,代理对象需要与目标对象实现一样的接口,当需要代理的对象很多的时候,就需要增加很多的类,假如代理接口需要新增一个方法,那么代理类和目标类都需要修改维护,那么有没有更好的解决方式呢?
动态代理
一、jdk动态代理
静态代理的代理类是我们在编译期就已经创建好了,而动态代理是是指代理类是程序在运行过程中创建的。jdk的动态代理是是基于接口的代理。
第一步:还是定义一个会谈接口:
/** * 会谈接口,有一个签约的方法 */ public interface Talk { public void sign(); }
第二步:定义一个球员,需要实现真正的签约方法
/** * 球员安东尼戴维斯,实现了签约的方法,需要本人亲自签约 */ public class Davis implements Talk { @Override public void sign() { System.out.println("签约了,5年2.25亿美元"); } }
第三步:定义一个实现了InvocationHandler接口的实现类,该类需要绑定目标类。
public class MyInvocationHandler implements InvocationHandler { //目标类(被代理的类) private Object target; public MyInvocationHandler(Object target){ this.target = target; } /** * * @param proxy 生成的代理类的实例 * @param method 被调用的方法对象 * @param args 调用method方法时传的参数 * @return method方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object object = null; System.out.println("我们拥有最后一年的球员选项"); //执行方法,相当于执行了Davis中的sign()方法, //当代理对象调用其方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 object = method.invoke(target,args); System.out.println("签约成功,交易否决权开始生效"); return object; } }
第四步:编写测试类:
public class DynamicProxyTest { public static void main(String[] args) { //被代理的对象 Talk talk = new Davis(); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(talk); //传入代理对象的字节码文件和接口类型,让Proxy来生成代理类 Talk davisProxy = (Talk)Proxy.newProxyInstance(talk.getClass().getClassLoader(), talk.getClass().getInterfaces(),myInvocationHandler); //调用签约方法 davisProxy.sign(); } }
控制台打印:
我们拥有最后一年的球员选项
签约了,5年2.25亿美元
签约成功,交易否决权开始生效
可以看到控制台和静态代理打印的一模一样。下面来分析一下jdk动态代理的原理:
我们看到,客户端通过Proxy的静态方法newProxyInstance生成了代理类,该方法有三个参数,分别是代理类的类加载器,这个代理类需要实现的接口以及一个处理器InvocationHandler,当我们使用代理类调用sign()方法的时候,是怎么执行这个方法的前置操作和后置操作,从代码来看,我们并没有显示的调用MyInvocationHandler的invoke方法,但是这个方法确实被执行了,究竟是哪里调用的呢?我们来看一下newProxyInstance的源码:
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
源码有一些检验判断,我们暂且忽略,重点是看怎么创建代理类以及代理类调用方法的时候如何调用的invoke方法的
重点剖析这行代码:
Class> cl = getProxyClass0(loader, intfs);
这个方法根据传入的类加载器和接口类型生成了一个类,这个类即是代理类。点进去:
private static Class> getProxyClass0(ClassLoader loader, Class>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
英文注释写到:如果缓存中有代理类了直接返回,否则将由ProxyClassFactory创建代理类。我们再来看一下ProxyClassFactory:
private static final class ProxyClassFactory implements BiFunction[], Class>> { // 所有代理类的前缀名都以$Proxy开头 private static final String proxyClassNamePrefix = "$Proxy"; // 下一个用于生成唯一代理类名的数字 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class> apply(ClassLoader loader, Class>[] interfaces) { Map , Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class> intf : interfaces) { /* */ Class> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* *验证该Class对象是不是接口 */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* *验证此接口不是重复的 */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * 记录非公共代理接口的包,以便代理类将在同一个包中定义 * 验证所有非公共代理接口都在同一个包中 */ for (Class> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * 为代理类选择一个全限定类名 */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * 生成代理类的字节码文件 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }
我们看到,最终调用了ProxyGenerator的generateProxyClass方法生成字节码文件。回到newProxyInstance方法中,我们看看这几行代码:
//代理类构造函数的参数类型 1、private static final Class>[] constructorParams = { InvocationHandler.class }; 2、final Constructor> cons = cl.getConstructor(constructorParams); 3、 cons.newInstance(new Object[]{h});
代理类实例化的代码在第三行,通过反射调用代理类对象的构造方法,选择了这个InvocationHandler为参数的构造方法,这个h就是我们传递过来的实现了InvocationHandler的实例。所以,我们猜测是生成的代理类持有我们前文定义的MyInvocationHandler实例,并调用里面的invoke方法。所以,我们通过反编译来看下生成的代理类的源码。
使用IDEA,在VM options一栏中输入:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,就会在项目中生成一个代理类:
package com.sun.proxy; import chenhuan.designpattern.proxy.staticproxy.Talk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; //该类继承了Proxy类,实现了Talk接口 public final class $Proxy0 extends Proxy implements Talk { //声明了一些Method变量,后面会用到 private static Method m1; private static Method m2; private static Method m3; private static Method m0; //代理类的构造方法调用父类的构造方法 public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } //实现sign()方法,注意传入的是m3, public final void sign() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); //加载Talk接口,并获取其sign方法 m3 = Class.forName("chenhuan.designpattern.proxy.staticproxy.Talk").getMethod("sign"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
至此,我们发现,代理类通过调super.h.invoke(this, m2, (Object[])null);执行我们实现的InvocationHandler接口的invoke方法。复盘下jdk动态代理的实现原理的几大步骤:
1、新建一个接口
2、为接口实现一个代理类
3、创建一个实现了InvocationHandler接口的处理器
4、通过Proxy的静态方法,根据类加载器,实现的接口,以及InvocationHandler处理器生成一个代理类
①为接口创建代理类的字节码文件
②使用ClassLoader将字节码文件加载到JVM
③创建代理类实例对象,执行对象的目标方法
我们看到,使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象,那么,有没有不需要目标类实现接口的动态代理呢?让我们来看下cglib动态代理。
二、cglib动态代理
使用cglib需要引入cglib jar包,本篇案例使用的是cglib2.2.jar
直接先来看下代码实现:
1、先定义一个目标类
package chenhuan.designpattern.proxy; public class Davis { public void sign() { System.out.println("签约了,5年2.25亿美元"); } }
2、定义一个拦截器,
public class MyMethodInterceptor implements MethodInterceptor { /** * * @param o 表示增强的对象,即实现这个接口类的一个对象 * @param method 表示要被拦截的方法 * @param objects 表示要被拦截方法的参数 * @param methodProxy * @return 表示要触发父类的方法对象 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("我们拥有最后一年的球员选项");
//调用代理类实例上的proxy方法的父类方法 Object object = methodProxy.invokeSuper(o,objects); System.out.println("签约成功,交易否决权开始生效"); return object; } }
3、使用字节码增强器来生成代理类:
public class CglibProxyTest { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); //设置enhancer的父类对象 enhancer.setSuperclass(Davis.class); //设置enhancer的回调对象,就是我们定义的拦截器 enhancer.setCallback(new MyMethodInterceptor()); //生成代理类 Davis davis = (Davis)enhancer.create(); davis.sign(); } }
cglib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。需要注意的是,cglib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
本文感觉篇幅过长,就不分析cglib动态代理的源码了 。
总结:静态代理由程序员创建代理类,在程序运行前代理类就已经存在了,并且代理类和目标类都要实现相同的接口。当需要代理的对象很多的时候,就需要增加很多的类,假如代理接口需要新增一个方法,那么代理类和目标类都需要修改维护,不易维护。jdk动态代理需要目标类实现接口,也就是说jdk动态代理只能对该类中实现了目标接口的方法进行代理,这个在实际编程中可能存在局限性,cglib动态代理完全不受代理类必须实现接口的限制,其生成的代理类是目标类的子类。