阅读完本文你能get到的知识点什么是JavassistJDK动态代理使用Javassist实现和JDK一样的效果什么是Javassist 很多同学估计会对这个词有点陌生,但随着你关注的博主越来越多,知道的也越来越多,马上这篇文章就带你走进Javassist的世界 Javassist和ASM一样是操作字节码的框架,Javassist诞生于1999年,多少有点年头 使用Javassist可以在运行时定义一个新类,可以在JVM加载类文件时修改类文件 而且Javassist提供不同类型的API:源码级别和字节码级别 本文使用源码级别的API,所以你甚至可以在不懂字节码的前提下使用它,入手相对简单 但在性能上略逊于ASMJDK动态代理 话不多说,先来回顾一下我们平时是怎么使用JDK动态代理的Proxy JDK提供一个类Proxy用于生成代理类 调用类方法newProxyInstance,传入的参数ClassLoaderloader:定义代理类的类加载器Classlt;?〔〕interfaces:代理类需要实现的所有接口InvocationHandlerh:调用处理程序,方法调用都会分派到这里 InvocationHandler InvocationHandler是一个接口类,定义了调用方法Objectproxy:生成的代理对象Methodmethod:接口方法实例Object〔〕args:方法调用中传递的参数值 如何调用 调用Proxy。newProxyInstance生成代理对象,传入参数接口InvocationHandler实现类的对象处理代理的逻辑 代码设计 在动手写代码之前,我们先花几分钟在脑海中设想一下我们需要生成的代理类是什么样子的? 这里先揭晓了 假设我们定义了一个接口类LoginService定义的接口类 那么我们需要生成一个大概是这样的代理类首先必须得实现了定义的接口LoginService接口的所有方法实现都调用都代理到InvocationHandler中 几个重要的类 从上面生成的代理类入手,我们生成的类 继承了父类MObject 实现了需要代理的接口LoginService 生成了类成员变量LoginService0,这个对应接口的定义的方法 实现了需要代理的接口方法login 还有带参数MInvocationHandler的构造方法MObject MObject是生成的代理类需要继承的父类,它的作用是存储了MInvocationHandler(处理程序接口) MInvocationHandler 同JDK自带的接口InvocationHandler,用于实现代理方法的处理逻辑 MProxy 同JDK自带的类Proxy 提供生成代理对象的方法newProxyInstance 编码开始 在经过代码设计之后,我们的脑海里应该有思路了,那就开始动手了 整个过程中比较重要的部分应该就是MProxy类了 在MProxy里面我们需要实现两大功能:生成代理类字节码根据字节码生成对象生成代理类字节码生成代理类名称生成空类给类设置需要实现的接口添加类成员变量实现接口方法 生成代理类名称 这一步相对简单,为了防止生成的代理类重名 这里拼接了所有需要代理的接口全限定类名,通过字符串连接 生成空类 首先我们需要根据新的类名生成一个空的类,注意类名不要重复了,不然会污染了原有的类 ClassPool:类池,存储所有类的信息,会将类名类信息存储到HashTable里,可通过ClassPool。getDefault()获取实例CtClass:代表一个类classPool。makeClass:生成新的类给类设置需要实现的接口 这里同样通过ClassPool的get方法获取到所有传入接口的CtClass定义 再调用setInterfaces方法给生成的类设置多个接口 添加类成员变量 因为我们调用MInvocationHandler的invoke方法时需要传入的第二个参数是被代理方法的Method实例 所以将这个方法的存储到类成员变量中 CtField代表着一个变量 传入类型、变量名生成一个CtField实例 通过setModifiers方法设置变量的修饰符为staticprivate 因为这里还要设置变量的值 调用getFieldInitCode生成初始化代码 为了获取Method对象,这里生成了反射的代码去获取 实例:Class。forName(类名)。getMethod(方法名,Classlt;?。。。方法的参数类型); 生成了类成员变量之后,接下来该到实现接口的方法了实现接口方法 这里需要实现接口的方法 可以通过CtNewMethod。copy方法去拷贝需要实现的方法,不要直接使用原来的CtMethod,防止污染 拿到新的CtMethod,我们需要设置它的方法体、设置修饰符为public 重点来看看怎么生成方法体代码 这里根据方法返回的类型调用不同的方法void:没有返回值的调用getMethodBodyCodeByVoid基本数据类型:调用getMethodBodyCodeByPrimitive其它类型:调用getMethodBodyCode 那按顺序来看,不需要返回值的 那我们需要生成的代码是长这样的 super。h。invoke(this,对应的类成员变量,newObject〔〕{方法参数}); 这个有个语法需要知道:0代表这方法的第一个参数,懂字节码的应该知道非构造方法的第一个入参是一个隐式的this,指向对象本身 newObject〔〕{}里面就可以用1112代表着方法的参数了 返回值是基本数据类型的,需要调用调用包装类型对应的拆箱方法如 Boolean。parseBoolean() 所以和上面生成步骤的区别在于前后生成了对于基本类型的parse代码 最后的返回其它类型的也比较简单 直接生成强转的代码如(String) 以上步骤走完,前期准备工作算是做完了,接下来就要根据生成的字节码来实例化对象了根据字节码生成对象 要根据字节码来生成对象,第一步我们需要编写自定义的类加载器,通过类加载器加载字节码编写自定义加载器MClassLoader,继承类ClassLoader提供add方法将类名映射到对应的字节数组重写ClassLoader类的findClass方法,使用我们生成的字节数组生成类 在MProxy中调用MClassLoader加载并实例化对象加载类mClassLoader。loadClass(clasName)获取带MInvocationHandler参数类型的构造实例化对象constructor。newInstance(h) 效果演示 好了,上面的代码已经编写完了,那么现在就来对比一下JDK自带的Proxy和我们自己实现的Proxy的效果我们定义一个需要代理的接口LoginService 这里按照Java的基本数据类型以及它们对应的包装类定义了16个接口方法 分别实现了代理类CusMInvocationHandler和CusJdkInvocationHandler 这两个代理类的实现是一样的 区别在于实现的接口一个是我们定义的MInvocationHandler 另一个是JDK的InvocationHandler 执行入口类 Main类分别生成了Proxy和MProxy的代理对象 然后执行代理对象的各个方法 来看看实现的效果,左边是JDK的动态代理,右边是使用Javassist实现的动态代理 作者:MinXie 链接:https:juejin。cnpost7168030376080703495