# / 代理模式 /java
近期在研究Hook技术,须要用到动态代理,说到动态代理就会聊到它的兄弟静态代理,那它们究竟是怎么一回事呢?实现方式有哪些呢?一块儿来看下node
代理在咱们生活中随处可见,好比咱们生活中的各类中介公司,以买一辆二手车为例:若是我买车,我能够本身去网上找车源,而后作质量检测,车辆过户等一系列的流程,可是这太费时间和精力了,我就是想花钱买个车而已,最后我竟然吭哧吭哧的干了这么多活;因而我能够经过中介买车,我只用给钱,选车就好了,其它活都交给中介干,这就是代理的一种实现;还有网络代理(无论是正向仍是反向代理),房屋租赁公司等都是代理的具体实现程序员
在Java编程里就有一种设计模式,即代理模式,提供了一种对目标对象的访问方式,即经过代理对象访问目标对象,代理对象是指具备与被代理对象相同的接口的类,客户端必须经过代理对象与被代理的目标类进行交互面试
代理类主要负责为目标类预处理消息、过滤消息、把消息转发给目标类,以及过后对返回结果的处理等。编程
代理类自己并不真正实现服务,而是同过调用目标类的相关方法,来提供特定的服务。真正的业务功能仍是由目标类来实现,可是能够在业务功能执行的先后加入一些公共的服务。例如咱们想给项目加入缓存、日志这些功能,咱们就可使用代理类来完成,而不必打开已经封装好的目标类。设计模式
代理实现方式:数组
若是按照代理建立的时期来进行分类的话, 能够分为静态代理、动态代理缓存
如图:网络
开源框架应用:架构
Spring框架是时下很流行的Java开源框架,Spring之全部如此流行,跟它自身的特性是分不开的,一个是IOC,一个是AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP能够对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度下降,提升程序的可重用性,同时提升了开发的效率。
/ 静态代理 /
静态代理是代理模式实现方式之一,比较简单,主要分为三个角色:客户端,代理类,目标类;而代理类须要与目标类实现同一个接口,并在内部维护目标类的引用,进而执行目标类的接口方法,并实如今不改变目标类的状况下前拦截,后拦截等所需的业务功能。
假设如今项目经理给你提了一个需求:在项目全部类的有关用户操做的方法先后打印日志;这种状况下就能够经过静态代理实现
作法就是:为每一个相关类都编写一个代理类(业务重合的能够共用一个代理类),而且让它们实现相同的接口
public interface ILogin { void userLogin(); }
定义目标类:
public class UserLogin implements ILogin { @Override public void userLogin() { System.out.print("用户登陆"); } }
定义代理类:
public class UserLoginProxy implements ILogin { private UserLogin mLogin; public UserLoginProxy() { mLogin = new UserLogin(); } @Override public void userLogin() { System.out.print("登陆前。。。"); mLogin.userLogin(); System.out.print("登陆后。。。"); } }
客户端:
public class UserLoginProxy implements ILogin { public class Test { public static void main(String[] args){ ILogin loginProxy = new UserLoginProxy(); loginProxy.userLogin(); } }
这样咱们就在尽可能不修改现有类的基础上实现了这个需求,同时客户端只用跟代理类打交道,彻底不用了解代理类与目标类的实现细节,也不须要知道目标类是谁;固然你若是想指定目标类,将在外部建立目标类,传给代理类
这里只是在同步状况下的一种实现,若是是异步操做,就须要进行一些改动
静态代理总结:
从静态代理的实现过程能够知道工做量太大,若是是在项目早期同步作还好,要是在接手老项目或者项目晚期再作,你可能要为成百上千个类建立对应的代理对象,那真的挺让人崩溃的;因此咱们就须要想有没有别的方法:如何少写或者不写代理类却能完成这些功能
作Java的都知道一个.java文件会先被编译成.class文件,而后被类加载器加载到JVM的方法区,存在形式是一个Class对象(所谓的Class对象就是Class文件在内存中的实例);
咱们构造出的任何对象实例是保存在Heap中,而实例是由其Class对象建立的(能够经过任意实例的getClass方法获取对应的Class对象),好比平时使用new关键字加构造方法Person p = new Person()就是将这个Class对象在Heap中建立一个实例;
能够看出要建立一个实例,最关键的是获得对应的Class对象,而获得Class对象追根溯源须要一个Java文件;那么咱们要作的就是不写Java文件,而是直接获得代理Class对象,而后根据它经过反射建立代理实例
Class对象里面包含了一个类的全部信息,好比构造器,方法,字段等;那咱们怎么在不写代理类的前提下获取到这些信息呢?
从静态代理的实现知道目标类和代理类实现了同一个接口,这是为了尽量保证代理对象的内部结构和目标对象一致,这样代理对象只须要专一于代码加强部分的编写,因此接口拥有代理对象和目标对象共同的类信息,那么咱们就能够从接口获取原本应该由代理类提供的信息,可是要命的是接口是不能实例化的啊
这就要讲到动态代理了:在动态代理中,不须要咱们再手动建立代理类,只须要编写一个动态处理器及指定要代理的目标对象实现的接口,真正的代理对象由JDK在运行时为咱们建立;JDK提供了java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy来实现动态代理
Proxy.getProxyClass(ClassLoader, interfaces)方法只须要接收一个类加载器和一组接口就能够返回一个代理Class对象,而后就能够经过反射建立代理实例;其原理就是从传入的接口Class中,拷贝类结构信息到一个新的Class对象中,并继承Proxy类,拥有构造方法;站在咱们的角度就是经过接口Class对象建立代理类Class对象
这里经过一个很骚的比喻来讲明下:一个东厂太监(接口Class对象)有一家子财产,可是为了侍奉皇帝这一伟大事业,毅然决然的割了DD(没有构造方法),虽然实现了本身的理想,可是不能繁育下一代(不能构造器建立对象),也就没有后人继承本身的家业;可是好在华佗在世,江湖上有一个高人(Proxy),发明了一个克隆大法(getProxyClass),不只克隆出了几乎和太监同样的下一代(新的Class),还拥有本身的小DD(构造方法),这样这个下一代就能继承太监的家产(类结构信息,实际上是实现了该接口),同时还能娶妻生子,传给下一代(建立实例)
用一副图展现动态代理和静态代理实现思路:
那这样就很简单了
public static Object loadProxy(Object target) throws Exception { //经过接口Class对象建立代理Class对象 Class<?> proxyClass = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces()); //拿到代理Class对象的有参构造方法 Constructor<?> constructors = proxyClass.getConstructor(InvocationHandler.class); //反射建立代理实例 Object proxy = constructors.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("执行前日志..."+"\n"); //执行目标类的方法 Object result = method.invoke(target, args); System.out.println("执行后日志..."+"\n"); return result; } }); return proxy; } public static void main(String[] args) throws Exception { ILogin proxy = (ILogin) loadProxy(new UserLogin()); proxy.userLogin(); }
看看打印结果
这样不管系统有多少目标类,经过传进来的目标类均可以获取到对应的代理对象,就达到咱们在执行目标类先后加日志的效果了,同时还不须要编写代理类
Proxy类还有个更简单的方法newProxyInstance,直接返回代理对象,以下
public static Object loadProxy(Object object) { return Proxy.newProxyInstance( object.getClass().getClassLoader(), //和目标对象的类加载器保持一致 object.getClass().getInterfaces(), //目标对象实现的接口,由于须要根据接口动态生成代理对象 new InvocationHandler() { //事件处理器,即对目标对象方法的执行 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("执行前日志..."); Object result = method.invoke(object, args); System.out.println("执行后日志..."); return result; } }); }
JDK动态代理总结:
Java的单继承机制决定了没法支持class的动态代理,也就意味着你拿到动态生成的代理对象,只能调用其实现的接口里的方法,没法像静态代理中的代理类能够在内部扩展更多的功能
动态生成代理对象原理
在动态代理的过程当中,咱们不能清晰的看到代理类的实际样子,并且被代理对象和代理对象是经过InvocationHandler来完成的代理过程,其中代理对象是如何生成的,具体什么样,为何代理对象执行的方法都会走到InvocationHandler中的invoke方法,要想了解这些就须要对Java是如何动态生成代理对象源码进行分析,继续往下看
大家有没有好奇getProxyClass这个方法是怎么经过【目标类实现的接口】生成代理Class对象的呢?
若是你在第一个方法输入以下代码
System.out.println("执行日志..."+(proxy instanceof Proxy)+"\n"); System.out.println("执行日志..."+(proxyClass.getName())+"\n"); System.out.println("执行日志..."+(proxyClass.getSuperclass().getName())+"\n"); System.out.println("执行日志..."+(proxyClass.getSuperclass().getSimpleName())+"\n"); System.out.println("执行日志..."+(proxyClass.getInterfaces()[0].getSimpleName())+"\n");
获得的结果是
从这四点是否是能知道点啥了:动态生成的代理对象的父类是Proxy,实现了ILogin接口;这也就是为何能将代理对象强转成ILogin,从而调用其接口方法;
也说明了为何只能支持动态生成接口代理,不能动态生成class代理,由于最终生成的代理对象确定会继承Proxy类,若是咱们提供的类已经继承了其它类,那就不能继承Proxy类了,动态代理也就无从谈起了
接下来再从源码分析下其原理,鉴于篇幅缘由,这里只分析重点代码
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); /** * 若是存在实现给定接口的给定加载器定义的代理类,则只返回缓存副本; 不然,它将经过ProxyClassFactory建立代理类 */ private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } return proxyClassCache.get(loader, interfaces); } private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // 全部代理类名称的前缀 private static final String proxyClassNamePrefix = "$Proxy"; // 用于生成惟一代理类名称的下一个数字 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) {//经过传入的接口clone后的接口 ...... String proxyPkg = null; // 定义代理class的包名 int accessFlags = Modifier.PUBLIC | Modifier.FINAL; for (Class<?> intf : interfaces) { int flags = intf.getModifiers();//获取接口修饰符 if (!Modifier.isPublic(flags)) {//咱们定义的接口修饰符都是public,因此这里不会进去 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) { // 若是没有非public代理接口,请使用com.sun.proxy包 proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * 生成代理class名称 * 好比com.sun.proxy.$Proxy0 */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * 经过接口和class名称生成代理class数据 * 若是继续看generateProxyClass,会发现里面是生成class数据,包括写入Object的三个初始方法、写入实现的接口、写入继承Proxy类等到ByteArrayOutputStream中,而后转换成byte数组返回,至因而否会将Byte数据写到class文件保存在本地,视状况而定(默认不保存到硬盘) */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { //将动态生成的class数据加载到内存中 生成一个代理class对象 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }
ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并无提供源代码,可是咱们可使用JD-GUI这样的反编译软件打开jrelibrt.jar来一探究竟,如下是其核心代码的分析:
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) { ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); final byte[] var4 = var3.generateClassFile(); //是否将生成的class数据保存到硬盘,默认不保存 if (saveGeneratedFiles) { ...... } return var4; } private byte[] generateClassFile() { //添加hashCode方法 this.addProxyMethod(hashCodeMethod, Object.class); //添加equals方法 this.addProxyMethod(equalsMethod, Object.class); //添加toString方法 this.addProxyMethod(toStringMethod, Object.class); Class[] var1 = this.interfaces; int var2 = var1.length; int var3; Class var4; //遍历接口数组 for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; Method[] var5 = var4.getMethods(); int var6 = var5.length; //添加接口里的方法,此时方法体还为空 for(int var7 = 0; var7 < var6; ++var7) { Method var8 = var5[var7]; this.addProxyMethod(var8, var4); } } Iterator var11 = this.proxyMethods.values().iterator(); List var12; while(var11.hasNext()) { var12 = (List)var11.next(); checkReturnTypes(var12); } Iterator var15; try { //添加一个带有InvocationHandler的构造方法 this.methods.add(this.generateConstructor()); var11 = this.proxyMethods.values().iterator(); //生成方法体代码 while(var11.hasNext()) { var12 = (List)var11.next(); var15 = var12.iterator(); while(var15.hasNext()) { ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next(); this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10)); //方法体里生成调用InvocationHandler的invoke方法代码 this.methods.add(var16.generateMethod()); } } this.methods.add(this.generateStaticInitializer()); } catch (IOException var10) { throw new InternalError("unexpected I/O Exception", var10); } if (this.methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } else if (this.fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } else { this.cp.getClass(dotToSlash(this.className)); this.cp.getClass("java/lang/reflect/Proxy"); var1 = this.interfaces; var2 = var1.length; //生成实现接口,继承Proxy类代码 for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; this.cp.getClass(dotToSlash(var4.getName())); } this.cp.setReadOnly(); ByteArrayOutputStream var13 = new ByteArrayOutputStream(); DataOutputStream var14 = new DataOutputStream(var13); try { var14.writeInt(-889275714); var14.writeShort(0); var14.writeShort(49); this.cp.write(var14); var14.writeShort(this.accessFlags); var14.writeShort(this.cp.getClass(dotToSlash(this.className))); var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy")); var14.writeShort(this.interfaces.length); Class[] var17 = this.interfaces; int var18 = var17.length; for(int var19 = 0; var19 < var18; ++var19) { Class var22 = var17[var19]; var14.writeShort(this.cp.getClass(dotToSlash(var22.getName()))); } var14.writeShort(this.fields.size()); var15 = this.fields.iterator(); while(var15.hasNext()) { ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next(); var20.write(var14); } var14.writeShort(this.methods.size()); var15 = this.methods.iterator(); while(var15.hasNext()) { ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next(); var21.write(var14); } var14.writeShort(0); return var13.toByteArray(); } catch (IOException var9) { throw new InternalError("unexpected I/O Exception", var9); } } }
从源码能够得出:
1. JDK帮咱们生成了这样一个class数据,它继承了Proxy类,添加了一个带InvocationHandler参数的构造方法,这样也就明白了为何使用构造方法反射建立代理对象的时候传入了一个InvocationHandler参数,由于默认会调用到Proxy类的构造方法,其参数正好是InvocationHandler,赋值给内部的成员变量h
protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; }
2. 实现了咱们指定的接口,并实现了接口里的方法,同时接口中的方法调用了InvocationHandler的invoke方法
3. 当代理对象执行方法的时候,方法里面都会执行InvocationHandler的invoke方法,咱们就能够在这里执行目标类方法
这样能够说动态生成代理class对象实际上是动态生成代理class数据
使用以下代码将动态生成的class数据保存到硬盘
public static void write(){ byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", UserLogin.class.getInterfaces()); String path = "D:/Workspaces/com/sun/proxy/LoginProxy.class"; try(FileOutputStream fos = new FileOutputStream(path)) { fos.write(classFile); fos.flush(); System.out.println("代理类class文件写入成功"); } catch (Exception e) { System.out.println("写文件错误"); } }
接下来将这个class文件反编译成Java文件看看:若是你没有jad工具,能够经过JAD Java Decompiler(https://varaneckas.com/jad/)下载,使用以下命令
/** * -d :用户指定输出文件保存目录 * d:\ :具体目录目录 * -sjava :输出文件扩展名 这里保存成Java文件 * D:\Workspaces\com\sun\proxy\LoginProxy.class :class文件 */ jad -d d:\ -sjava D:\Workspaces\com\sun\proxy\LoginProxy.class /** * 上面的命令没有指定保存的Java文件名,下面的命令能够指定保存文件名 * -p 输出详细文件信息 */ jad -p D:\Workspaces\com\sun\proxy\LoginProxy.class > D:\Workspaces\com\sun\proxy\LoginProxy.java
代理类:
public final class $Proxy0 extends Proxy implements ILogin { public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } public final boolean equals(Object obj) { try { return ((Boolean)super.h.invoke(this, m1, new Object[] { obj })).booleanValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void userLogin() { try { super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)super.h.invoke(this, m0, null)).intValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } private static Method m1; private static Method m2; private static Method m3; private static Method m0; static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.mango.demo.proxy.ILogin").getMethod("userLogin", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch(NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch(ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } }
至此就解答了这一节【动态生成代理对象原理】刚开始提出的几个疑问了
JDK动态代理是实现AOP编程的一种途径,能够在执行指定方法先后贴入本身的逻辑,像Spring、Struts2就有用到该技术(拦截器设计就是基于AOP的思想);只不过JDK动态代理只能实现基于接口的动态代理,也算是一个遗憾,还有这种实现方式也不能避免类数量增长,由于你必需要为每一个业务类编写业务接口;那么有没有不用写代理类、也不用写业务接口的代理方法呢?
方法是有的,须要用到CGLib了:
CGLIB(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库,它能够在运行期扩展Java类与实现Java接口CGLIB比JDK的代理更增强大,不仅能够实现接口,还能够扩展类,经过字节码技术为一个类建立子类,并在子类中采用方法拦截的技术拦截全部父类方法的调用,顺势植入横切逻辑。但由于采用的是继承,因此不能对final修饰的类进行代理
CGLIB底层封装了ASM,经过对字节码的操做来生成类,具备更高的性能,可是CGLIB建立代理对象时所花费的时间却比JDK多;ASM是一套JAVA字节码生成架构,可以动态生成.class文件并在加载进内存以前进行修改
使用CGLIB须要引用jar包cglib-nodep-3.2.5.jar(若是引入cglib.jar,还须要引入asm的jar包)
像Spring框架都支持这两种动态代理方式,但Spring默认使用JDK动态代理,在须要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过如今的项目都是面向接口编程,因此JDK动态代理相对来讲用的仍是多一些。
至于在咱们本身的业务里须要选择哪一种方式实现AOP,仍是须要视状况而定,两种方式组合使用或许能支持更多的业务需求。