Java是一门静态语言,通常,我们需要的class在编译的时候就已经生成了,为什么有时候我们还想在运行时动态生成class呢?

因为在有些时候,我们还真得在运行时为一个类动态创建子类。比如,编写一个ORM框架,如何得知一个简单的JavaBean是否被用户修改过呢?

以User为例:

public classUser {privateString id;privateString name;publicString getId() {returnid;

}public voidsetId(String id) {this.id =id;

}publicString getName() {returnname;

}public voidsetName(String name) {this.name =name;

}

}

其实UserProxy实现起来很简单,就是创建一个User的子类,覆写所有setXxx()方法,做个标记就可以了:

public class UserProxy extendsUser {private booleandirty;public booleanisDirty() {return this.dirty;

}public void setDirty(booleandirty) {this.dirty =dirty;

}

@Overridepublic voidsetId(String id) {super.setId(id);

setDirty(true);

}

@Overridepublic voidsetName(String name) {super.setName(name);

setDirty(true);

}

}

但是这个UserProxy就必须在运行时动态创建出来了,因为编译时ORM框架根本不知道User类。

现在问题来了,动态生成字节码,难度有多大?

如果我们要自己直接输出二进制格式的字节码,在完成这个任务前,必须先认真阅读JVM规范第4章,详细了解class文件结构。估计读完规范后,两个月过去了。

所以,第一种方法,自己动手,从零开始创建字节码,理论上可行,实际上很难。

第二种方法,使用已有的一些能操作字节码的库,帮助我们创建class。

目前,能够操作字节码的开源库主要有CGLib和Javassist两种,它们都提供了比较高级的API来操作字节码,最后输出为class文件。

比如CGLib,典型的用法如下:

Enhancer e = newEnhancer();

e.setSuperclass(...);

e.setStrategy(newDefaultGeneratorStrategy() {protectedClassGenerator transform(ClassGenerator cg) {return newTransformingGenerator(cg,new AddPropertyTransformer(new String[]{ "foo"},newClass[] { Integer.TYPE }));

}});

Object obj= e.create();

比自己生成class要简单,但是,要学会它的API还是得花大量的时间,并且,上面的代码很难看懂对不对?

有木有更简单的方法?

有!

换一个思路,如果我们能创建UserProxy.java这个源文件,再调用Java编译器,直接把源码编译成class,再加载进虚拟机,任务完成!

毕竟,创建一个字符串格式的源码是很简单的事情,就是拼字符串嘛,高级点的做法可以用一个模版引擎。

如何编译?

Java的编译器是javac,但是,在很早很早的时候,Java的编译器就已经用纯Java重写了,自己能编译自己,行业黑话叫“自举”。从Java 1.6开始,编译器接口正式放到JDK的公开API中,于是,我们不需要创建新的进程来调用javac,而是直接使用编译器API来编译源码。

使用起来也很简单:

JavaCompiler compiler =ToolProvider.getSystemJavaCompiler();int compilationResult = compiler.run(null, null, null, '/path/to/Test.java');

这么写编译是没啥问题,问题是我们在内存中创建了Java代码后,必须先写到文件,再编译,最后还要手动读取class文件内容并用一个ClassLoader加载。

有木有更简单的方法?

有!

其实Java编译器根本不关心源码的内容是从哪来的,你给它一个String当作源码,它就可以输出byte[]作为class的内容。

所以,我们需要参考Java Compiler API的文档,让Compiler直接在内存中完成编译,输出的class内容就是byte[]。

代码改造如下:

Mapresults;

JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();

StandardJavaFileManager stdManager= compiler.getStandardFileManager(null, null, null);try (MemoryJavaFileManager manager = newMemoryJavaFileManager(stdManager)) {

JavaFileObject javaFileObject=manager.makeStringSource(fileName, source);

CompilationTask task= compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));if(task.call()) {

results=manager.getClassBytes();

}

}

上述代码的几个关键在于:

用MemoryJavaFileManager替换JDK默认的StandardJavaFileManager,以便在编译器请求源码内容时,不是从文件读取,而是直接返回String;

用MemoryOutputJavaFileObject替换JDK默认的SimpleJavaFileObject,以便在接收到编译器生成的byte[]内容时,不写入class文件,而是直接保存在内存中。

最后,编译的结果放在Map中,Key是类名,对应的byte[]是class的二进制内容。

为什么编译后不是一个byte[]呢?

因为一个.java的源文件编译后可能有多个.class文件!只要包含了静态类、匿名类等,编译出的class肯定多于一个。

如何加载编译后的class呢?

加载class相对而言就容易多了,我们只需要创建一个ClassLoader,覆写findClass()方法:

class MemoryClassLoader extendsURLClassLoader {

Map classBytes = new HashMap();public MemoryClassLoader(MapclassBytes) {super(new URL[0], MemoryClassLoader.class.getClassLoader());this.classBytes.putAll(classBytes);

}

@Overrideprotected Class> findClass(String name) throwsClassNotFoundException {byte[] buf =classBytes.get(name);if (buf == null) {return super.findClass(name);

}

classBytes.remove(name);return defineClass(name, buf, 0, buf.length);

}

}

除了写ORM用之外,还能干什么?

可以用它来做一个Java脚本引擎。实际上本文的代码主要就是参考了Scripting项目的源码。

完整的源码呢?

也就200行代码吧!动态创建class不是梦!

动态生成java类_Java 运行时动态生成class相关推荐

  1. 怎样用uml类图生成java类_JAVA:面向对象编程的底层逻辑,深度剖析

    什么是面向对象 在目前的软件开发领域有两种主流的开发方法,分别是结构化开发方法和面向对象开发方法.早期的编程语言如C.Basic.Pascal等都是结构化编程语言,随着软件开发技术的逐渐发展,人们发现 ...

  2. java 致命错误_Java运行时环境检测到致命错误:SIGSEGV(0xb)

    我正在使用64位操作系统的RHEL 6.对于我的一个应用程序,我安装了"jre-6u23-linux-x64.bin".当我执行我的应用程序时,我得到下面的错误: # A fata ...

  3. 运用Hibernate-Tools自动生成Java类和schema时,出现not found while looking for property...异常...

    问题描述: 在使用Hibernate-tools时出现not found while looking for property...(具体信息省略). 问题分析: 我找到出错对应的hbm.xml文件, ...

  4. .NET6运行时动态更新限流阈值

    自FireflySoft.RateLimit发布以来,帮助了不少需要在.net中进行限流处理的用户.前段时间有个开发者发了一个pull request,大意是Redis重启的时候Lua script会 ...

  5. 【java】Java运行时动态生成类几种方式

    1.概述 转载:Java运行时动态生成类几种方式 这里发现自己不知道的,原来Java 还能自己编译自己,学到了. 最近一个项目中利用规则引擎,提供用户拖拽式的灵活定义规则.这就要求根据数据库数据动态生 ...

  6. 【Java进阶】有哪些方法可以在运行时动态生成一个Java类?

    在开始今天的学习前,我建议你先复习一下专栏第 6 讲有关动态代理的内容.作为 Java 基础模块中的内容,考虑到不同基础的同学以及一个循序渐进的学习过程,我当时并没有在源码层面介绍动态代理的实现技术, ...

  7. Java运行时动态加载类之URLClassLoader

    需求场景:通过URLClassLoader从jar文件中加载类并创建实例,可实现运行时动态加载 1.要加载的jar: 1)接口类IC package cn.fjs;public interface I ...

  8. 运用delphiXE RTTI在运行时动态获取信息及获取某个TComponent类或TObject类的RttiType信息的案例

    运用delphiXE RTTI在运行时动态获取信息及获取某个TComponent类或TObject类的RttiType信息的案例 一.理解RTTI 先看看官方文档:http://docwiki.emb ...

  9. SAP UI5 应用开发教程之五十八 - 使用工厂方法在运行时动态创建不同类型的列表行项目控件试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

最新文章

  1. HDU 1544 Palindromes(回文子串)
  2. C++虚继承和虚基类详解(二)
  3. java date oracle_java对象属性为date oracle数据库字段为Timestamp 处理方式
  4. php采集一言代码_PHP简单实现一言 / 随机一句功能
  5. 【干货分享】推荐5个可以让你事半功倍的Python自动化脚本
  6. C++程序设计:原理与实践(进阶篇)15.6 实例:一个简单的文本编辑器
  7. css 剩余空间,CSS DIV高度撑满剩余空间
  8. spring mvc异步操作处理,注解方式
  9. 极域电子书包课堂管理系统怎么控屏_极域电子教室使用方法
  10. 分享76网络科技88教育教学47公司企业PPT模板
  11. sqlServer基础知识
  12. 02-Spring的核心API
  13. 用户体验优化事半功倍:如何绘制客户行为轨迹图
  14. 关于阻止迅雷上传,带慢计算机的工具
  15. 华为云灾备方案,如何保障企业数据安全
  16. DirectD3D-光照和材质
  17. java实现九宫格解锁_Java计算手机九宫格锁屏图案连接9个点的方案总数
  18. 联发科技一道笔试题目
  19. node中http的请求数据访问在浏览器中的基本使用方法和例子/静态资源库/url的基本使用/mime.json的内容和使用方式
  20. QA:MEID号申请、什么是MEID号、怎么申请MEID号、MEID号获取

热门文章

  1. JAVA 并发编程实践 - 原子变量与非阻塞同步机制 笔记
  2. 启动和停止数据库——停止例程
  3. 使用 Apache Hadoop 处理日志
  4. ICMP报文类型和代码
  5. 测速源码_物联网之智能平衡车开发实战项目(附源码)
  6. android上传文件用哪个布局,每周总结20130821——android控件的尺寸、http文件上传...
  7. python dendrogram_收藏 | Python数据可视化的一些简单总结
  8. python输入数字输出中文_go格式“占位符”, 输入输出,类似python的input
  9. 软件测试是什么,测试从一个点出发。
  10. oracle 自动化脚本,分享一些非常有用的oracle脚本