作者简介

传恒,一个喜欢摄影和旅游的软件工程师,先后从事饿了么物流蜂鸟自配送和蜂鸟众包的开发,现在转战 Java,目前负责物流策略组分流相关业务的开发。

什么是动态编程

动态编程是相对于静态编程而言的,平时我们讨论比较多的静态编程语言例如Java,
与动态编程语言例如JavaScript相比,二者有什么明显的区别呢?
简单的说就是在静态编程中,类型检查是在编译时完成的,而动态编程中类型检查是在运行时完成的,
所谓动态编程就是绕过编译过程在运行时进行操作的技术。

动态编程使用场景

  • 通过配置生成代码,减少重复编码,降低维护成本。
  • AOP的一种实现方式,方便实现性能监控和分析,日志,事务,权限校验等。
  • 实现新语言的语义,例如Groovy使用ASM生成字节码。
  • 单元测试中动态mock测试依赖。

在Java中有如下几种方式实现动态编程:

反射

我们常用到的动态特性主要是反射,在运行时查找对象的属性和方法,修改作用域,通过方法名称调用方法等。在线的应用不建议频繁使用反射,因为反射的性能开销较大。

动态代理

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

动态编译

动态编译是从Java 6开始支持的,主要是通过一个JavaCompiler接口来完成的。通过这种方式我们可以直接编译一个已经存在的java文件,也可以在内存中动态生成Java代码,动态编译执行。

调用Java Script引擎

Java 6加入了对Script(JSR223)的支持。这是一个脚本框架,提供了让脚本语言来访问Java内部的方法。你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本,这个脚本API允许你为脚本语言提供Java支持。

动态生成字节码

操作java字节码的工具有BECL/ASM/CGLIB/Javassist,其中有两个比较流行的,一个是ASM,一个是Javassist。
ASM直接操作字节码指令,执行效率高,要求使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。
Javassist提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低,所以接下来我们重点讲讲Javassist。

Javassist


Javassist是一个开源的分析、编辑和创建Java字节码的类库。
它是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶滋) 所创建的,目前已经加入到开放源代码JBoss应用服务器项目,JBoss通过使用Javassist对字节码进行操作,实现动态AOP框架。

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中。

如果事先知道要修改哪些类,修改类的最简单方法如下:

  1. 调用 ClassPool.get() 获取 CtClass 对象
  2. 修改对象
  3. 调用 CtClass 对象的 writeFile() 或者 toBytecode() 获得修改过的类文件。

如果需要定义一个新类,只需要

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("HelloWorld");

冻结classes

如果一个 CtClass 对象通过 writeFile(), toClass(), toBytecode()被转换成一个类文件,该CtClass对象会被冻结起来,不允许再修改,因为一个类只能被JVM加载一次。

CtClasss cc = ...;:
cc.writeFile();
cc.defrost();
cc.setSuperclass(...);    // 类已经被解冻

Class 搜索路径:

通过 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。

参考文档:

  • https://www.javassist.org/tutorial/tutorial.html
  • https://en.wikipedia.org/wiki/Javassist

阅读博客还不过瘾?

欢迎大家扫二维码通过添加群助手,加入交流群,讨论和博客有关的技术问题,还可以和博主有更多互动

博客转载、线下活动及合作等问题请邮件至 shadowfly_zyl@hotmail.com 进行沟通

Java动态编程初探相关推荐

  1. Java动态编程初探——Javassist

    最近需要通过配置生成代码,减少重复编码和维护成本.用到了一些动态的特性,和大家分享下心得. 我们常用到的动态特性主要是反射,在运行时查找对象属性.方法,修改作用域,通过方法名称调用方法等.在线的应用不 ...

  2. Java动态编程技术

    Java是一种静态显式强类型语言,导致java代码存在大量的模版化代码,一直被人诟病开发效率低.其实java语言本身以及JVM生态提供了大量动态编程技术,可以大量减少模版化代码.本文尝试整理相关的技术 ...

  3. 秒懂Java动态编程(Javassist简单入门级)

    概述 什么是动态编程?动态编程解决什么问题?Java中如何使用?什么原理?如何改进? 什么是动态编程 动态编程是相对于静态编程而言的,平时我们讨论比较多的就是静态编程语言,例如Java,与动态编程语言 ...

  4. java动态编程解决分硬币问题,动态编程硬币更改问题

    我在理解各种问题的动态编程解决方案时遇到问题,特别是硬币找零问题: "给定值N,如果我们要N分钱找零,并且我们有无限数量的S = {S1,S2,..,Sm}硬币的供应,我们可以用几种方法进行 ...

  5. Java是动态语言吗?从《Java核心编程》探索真知

    目录 一.Java是动态语言吗? 1.动态语言 2.静态类型 3.<Java核心编程>中探索~~为什么Java可以称之为"准动态语言"? 二.了解ClassLoader ...

  6. 【Java高级】初探socket编程 ——JavaSocket连接与简单通信

    新学期生活开始一段时间了,要继续学习一些新的技术(这里指socket /doge),目标是尝试完成一个在线即时聊天的小程序(尽量不咕).会更新一系列socket编程的技术文章,欢迎关注交流~ 那么千里 ...

  7. AspectJ和Spring AOP(java动态代理和CGLIB)简单介绍

    1.AOP介绍 什么是AOP:AOP就是面向切面编程.使用的背景: 1)我们的振隆维护着一千个方法,一天老板让振隆把这一千个方法都要加上事务代码(统一代码) 2)振隆咬咬牙,添加了一个新的方法,然后让 ...

  8. 基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程

    许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存.CPU.缓存等予以说明.实际上,在实际的 ...

  9. 《Java并发编程实践》学习笔记之一:基础知识

    <Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念:  (2)进程:是一种活动,它是由一个动作序列组成 ...

最新文章

  1. 分布式深度学习最佳入门(踩坑)指南
  2. 闲谈IPv6-典型特征的一些技术细节
  3. 提交PR后修改内容并合并commit
  4. Tomcat配置Web虚拟目录
  5. springboot使用TemplateEngine修改邮箱后发送验证码示例
  6. full GC排查案例
  7. python不满足条件重新输入_在python里为何不满足while条件,while里语句却可以执行?...
  8. 爬虫如何监听插件_Go 爬虫之 colly 从入门到不放弃指南
  9. CC.Net 全接触系列之二: CCRC.Net : CC.Net 的一种另类使用方式
  10. 51nod 1686 第K大区间 二分瞎搞
  11. 中国首档程序员综艺:你有freebug吗?
  12. C# 给picturebox添加滚动条
  13. oracle主键与索引,oracle 主键 \索引
  14. 黑客入门,从HTB开始
  15. 增大或者减小图片大小的方法
  16. typescript元组
  17. 青春是一列不再回头的火车…
  18. linux摄像头驱动调试,linux 2.6.21支持vimcro摄像头驱动的调试过程
  19. 从零开始部署深信服EDR
  20. ggplot2绘制数据分布crossbar图教程

热门文章

  1. 1.2 微型计算机的分类
  2. #undef_Cplusplus
  3. 申宝证券-个股涨跌对半开
  4. Loadrunner进行手机APP压力测试(亲测可行)
  5. 一个有趣的原型软件Balsamiq Mockups
  6. python链表的实现
  7. Virtualized Firewall for Cloud Security: Working on Machines Identities, NOT on Their IP Addresses
  8. css 背景图片自适应全屏
  9. C语言经典100例---实例九:输出国际象棋棋盘
  10. 知乎部署系统演进简史