做者简介html
传恒,一个喜欢摄影和旅游的软件工程师,前后从事饿了么物流蜂鸟自配送和蜂鸟众包的开发,如今转战 Java,目前负责物流策略组分流相关业务的开发。java
动态编程是相对于静态编程而言的,平时咱们讨论比较多的静态编程语言例如Java, 与动态编程语言例如JavaScript相比,两者有什么明显的区别呢? 简单的说就是在静态编程中,类型检查是在编译时完成的,而动态编程中类型检查是在运行时完成的, 所谓动态编程就是绕过编译过程在运行时进行操做的技术。编程
咱们经常使用到的动态特性主要是反射,在运行时查找对象的属性和方法,修改做用域,经过方法名称调用方法等。在线的应用不建议频繁使用反射,由于反射的性能开销较大。服务器
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,经过这个类和这个接口能够生成JDK动态代理类和动态代理对象。架构
动态编译是从Java 6开始支持的,主要是经过一个JavaCompiler接口来完成的。经过这种方式咱们能够直接编译一个已经存在的java文件,也能够在内存中动态生成Java代码,动态编译执行。框架
Java 6加入了对Script(JSR223)的支持。这是一个脚本框架,提供了让脚本语言来访问Java内部的方法。你能够在运行的时候找到脚本引擎,而后调用这个引擎去执行脚本,这个脚本API容许你为脚本语言提供Java支持。编程语言
操做java字节码的工具备BECL/ASM/CGLIB/Javassist,其中有两个比较流行的,一个是ASM,一个是Javassist。 ASM直接操做字节码指令,执行效率高,要求使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。 Javassist提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低,因此接下来咱们重点讲讲Javassist。编辑器
Javassist(Java Programming Assistant) 使对Java字节码的操做变得简单,它使Java程序可以在运行时定义新类,而且能够在JVM加载时修改类文件。 与其它相似的字节码编辑器不一样,它提供两个级别的API:源级别和字节码级别。 若是用户使用源级别API,他们能够在不知道Java字节码规范的状况下编辑类文件。整个API仅使用Java语言的词汇表进行设计,你甚至可使用Java源代码的方式插入字节码。 另外,用户也可使用字节码级别的API去直接编辑类文件。工具
// ClassPool 是 CtClass 对象的容器,存储着CtClass的Hash表。它按需读取类文件来构造CtClass对象,而且保存CtClass对象以便以后使用
ClassPool classPool = ClassPool.getDefault();
// CtClass 表示一个class文件,一个 GtClass(compile-time class)对象用来处理一个class文件,下面是从classpath中查找该类
CtClass ctClass = classPool.get("test.config.ConfigHandle");
// 通知编辑器去寻找对应的包
classPool.importPackage("org.mockito.Mockito");
classPool.importPackage("test.adapter.ext.IDowngrade");
classPool.importPackage("test.utils.property.IProperties");
// 使用removeField() removeMethod() 去删除对应的属性和方法
ctClass.removeField(ctClass.getDeclaredField("serviceHandle"));
ctClass.removeField(ctClass.getDeclaredField("switchHandle"));
ctClass.removeField(ctClass.getDeclaredField("configHandle"));
// CtMethod 和 CtConstructor 提供了 setBody() 方法去修改方法体
CtConstructor ctConstructor = ctClass.getDeclaredConstructors()[0];
ctConstructor.setBody("{this.mySwitch = Mockito.mock(IDowngrade.class);\n" +
" this.myConfig = Mockito.mock(IProperties.class);}");
// toClass() 请求当前线程的 ClassLoader 去加载 CtClass 所表明的类文件
ctClass.toClass();
//输出成二进制格式
//byte[] b = ctClass.toBytecode();
//输出class文件到目录中
//ctClass.writeFile("/tmp");
复制代码
ClassPool是CtClass对象的容器,由于编译器在编译引用CtClass表明的Java类的源代码时,可能会引用CtClass对象,因此一旦一个CtClass被建立,它就被保存在ClassPool中。性能
若是事先知道要修改哪些类,修改类的最简单方法以下:
若是须要定义一个新类,只须要
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("HelloWorld");
复制代码
若是一个 CtClass 对象经过 writeFile(), toClass(), toBytecode()被转换成一个类文件,该CtClass对象会被冻结起来,不容许再修改,由于一个类只能被JVM加载一次。
CtClasss cc = ...;
:
cc.writeFile();
cc.defrost();
cc.setSuperclass(...); // 类已经被解冻
复制代码
经过 ClassPool.getDefault() 获取的ClassPool默认使用JVM的类搜索路径。若是程序运行在JBoss或者Tomcat等Web服务器上,ClassPool可能没法找到用户本身定义的类,由于这种Web服务器使用多个类加载器做为系统类加载器。在这种状况下,ClassPool必须添加额外的类搜索路径。
pool.insertClassPath(new ClassClassPath(this.getClass())); // 当前的类使用的类路径,注册到类搜索路径
pool.insertClassPath("/usr/local/javalib"); // 添加目录 /usr/local/javalib 到类搜索路径
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp); // 注册URL到搜索路径
复制代码
在Java中,多个类加载器是能够共存的。每一个类加载器建立了本身的命名空间,不一样的类加载器能够加载具备相同类名的不一样类文件,被加载的类也会被视为不一样的类。此功能使咱们可以在单个JVM上面运行多个应用程序,即便这些程序包含具备相同名称的类。
注意,JVM不容许动态从新加载类,一旦类加载器加载了一个类,就不能再在运行时从新加载该类的其它版本。所以,在JVM加载类以后,就不能再更改该类的定义。 可是,JPDA(Java平台调试器架构)提供有限的从新加载类的能力,若是相同的类文件由两个不一样的类加载器加载,则JVM内会建立两个具备相同名称可是定义的不一样的类。因为两个类不相同,因此一个类的实例不能被分配给另外一个类的变量,两个类之间的转换操做也会失败而且抛出一个ClassCastException异常。
Javassist比咱们在本文中所讨论的功能要丰富得多,做为jboss的一个子项目,其主要的优势在于简单和快速,能够直接使用java编码的形式,而不须要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。若是你不是很了解虚拟机指令,能够采用javassist。
阅读博客还不过瘾?
欢迎你们扫二维码经过添加群助手,加入交流群,讨论和博客有关的技术问题,还能够和博主有更多互动
博客转载、线下活动及合做等问题请邮件至 shadowfly_zyl@hotmail.com 进行沟通 ![]()