[转] Java运行时动态生成class的方法
[From] http://www.liaoxuefeng.com/article/0014617596492474eea2227bf04477e83e6d094683e0536000
廖雪峰 / 编程 / 2016-4-27 20:45 / 阅读: 2636
Java是一门静态语言,通常,我们需要的class在编译的时候就已经生成了,为什么有时候我们还想在运行时动态生成class呢?
因为在有些时候,我们还真得在运行时为一个类动态创建子类。比如,编写一个ORM框架,如何得知一个简单的JavaBean是否被用户修改过呢?
以User为例:
public class User {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
其实UserProxy实现起来很简单,就是创建一个User的子类,覆写所有setXxx()方法,做个标记就可以了:
public class UserProxy extends User {
private boolean dirty;
public boolean isDirty() {
return this.dirty;
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
@Override
public void setId(String id) {
super.setId(id);
setDirty(true);
}
@Override
public void setName(String name) {
super.setName(name);
setDirty(true);
}
}
但是这个UserProxy就必须在运行时动态创建出来了,因为编译时ORM框架根本不知道User类。
现在问题来了,动态生成字节码,难度有多大?
如果我们要自己直接输出二进制格式的字节码,在完成这个任务前,必须先认真阅读JVM规范第4章,详细了解class文件结构。估计读完规范后,两个月过去了。
所以,第一种方法,自己动手,从零开始创建字节码,理论上可行,实际上很难。
第二种方法,使用已有的一些能操作字节码的库,帮助我们创建class。
目前,能够操作字节码的开源库主要有CGLib和Javassist两种,它们都提供了比较高级的API来操作字节码,最后输出为class文件。
比如CGLib,典型的用法如下:
Enhancer e = new Enhancer();
e.setSuperclass(...);
e.setStrategy(new DefaultGeneratorStrategy() {
protected ClassGenerator transform(ClassGenerator cg) {
return new TransformingGenerator(cg,
new AddPropertyTransformer(new String[]{ "foo" },
new Class[] { 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[]。
代码改造如下:
Map<String, byte[]> results;
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
try (MemoryJavaFileManager manager = new MemoryJavaFileManager(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<String, byte[]>中,Key是类名,对应的byte[]是class的二进制内容。
为什么编译后不是一个
byte[]呢?
因为一个.java的源文件编译后可能有多个.class文件!只要包含了静态类、匿名类等,编译出的class肯定多于一个。
如何加载编译后的class呢?
加载class相对而言就容易多了,我们只需要创建一个ClassLoader,覆写findClass()方法:
class MemoryClassLoader extends URLClassLoader {
Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
public MemoryClassLoader(Map<String, byte[]> classBytes) {
super(new URL[0], MemoryClassLoader.class.getClassLoader());
this.classBytes.putAll(classBytes);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
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项目的源码。
完整的源码呢?
在这里:https://github.com/michaelliao/compiler,连Maven的包都给你准备好了!
也就200行代码吧!动态创建class不是梦!
[转] Java运行时动态生成class的方法的更多相关文章
- Java 运行时动态生成class
转载 http://www.liaoxuefeng.com/article/0014617596492474eea2227bf04477e83e6d094683e0536000 Java是一门静态语言 ...
- 如何获取java运行时动态生成的class文件?
查看运行时生成的文件,以更清楚运行情况. 查看动态生成的类,一般有两个方法: 1. 使用据说是jdk自带包sa-jdi.jar里的工具. 其中,不想自己搞,当然就利用下,sa-jdi.jar 里自带的 ...
- Java运行时动态加载类之ClassLoader
https://blog.csdn.net/fjssharpsword/article/details/64922083 *************************************** ...
- 理解JVM之JAVA运行时内存区域
java运行时内存区域划分为方法区,堆区,虚拟机栈区,本地方法栈,程序计数器.其中方法区跟堆区是线程共享的数据区,其他的是线程私有的数据区. 1.程序计数器 程序计数器(PC)是一块较小的内存,他是存 ...
- 使用javassist运行时动态重新加载java类及其他替换选择
在不少的情况下,我们需要对生产中的系统进行问题排查,但是又不能重启应用,java应用不同于数据库的存储过程,至少到目前为止,还不能原生的支持随时进行编译替换,从这种角度来说,数据库比java的动态性要 ...
- 读书笔记-浅析Java运行时数据区
作为一个 Java 为主语言的程序员,我偶尔也需要 用 C/C++ 写程序,在使用时让我很烦恼的一件事情就是需要对 new 出来的对象进行 delete/free 操作,我老是担心忘了这件事情,从而导 ...
- 【转】Java运行时数据区简介及堆与栈的区别
理解JVM运行时的数据区是Java编程中的进阶部分.我们在开发中都遇到过一个很头疼的问题就是OutOfMemoryError(内存溢出错误),但是如果我们了解JVM的内部实现和其运行时的数据区的工作机 ...
- JVM发展史和Java运行时内存区域
目前三大主流JVM: Sun HotSpot:Sun于1997年收购Longview Technologies公司所得.Sun于2009年被Oracle收购. BEA JRockit:BEA于2002 ...
- java运行时内存模式学习
学习java运行时内存模式: 各区介绍: 方法区(线程共享):用于存放被虚拟机加载的类的元数据:静态变量,常量,以及编译和的代码(字节码),也称为永久代(所有该类的实例被回收,或者此类classLoa ...
随机推荐
- Luogu 3066 [USACO12DEC]逃跑的BarnRunning Away From…
好像是某CF的题,不记得…… 很套路的题,但是觉得可以做一下笔记. 倍增 + 差分. 有一个比较简单的思路就是每一个点$x$向上走一走,直到走到一个点$y$使总路程恰好不超过超过了$L$,然后把$(x ...
- hdu 1556 Color the ball(非线段树做法)
#include<stdio.h> #include<string.h> ]; int main() { int n,i; int a,b; while(scanf(" ...
- .NET将服务器文件导出
导出文件: string filePath = Server.UrlDecode(filePath); if (File.Exists(fi ...
- 函数有一个特殊的属性 prototype!
JavaScript 中只有对象,没有类. 实际上,JavaScript 才是真正应该被称为「面向对象」的语言,因为它是少有的可以不通过类,直接创建对象的语言. 函数的 prototype 属性 在 ...
- python3安装 feedparser
在看<集体智慧编程>时碰到python3环境下安装feedparser的问题,搜索发现很多人碰到此问题,最终找以下方法解决. how to install feedparser on py ...
- Usage of the @ (at) sign in ASP.NET
from:http://www.mikesdotnetting.com/article/258/usage-of-the-at-sign-in-asp-net Thursday, January 22 ...
- [Lua快速了解一下]Lua的控制语句
-Lua中没有++或者--的骚操作 -while loop sum = num = do sum = sum + num num = num + end print("sum =" ...
- Sharepoint2013搜索学习笔记之自定义结果显示模板(九)
搜索结果通过套用定义好的显示模板来展示结果,显示模板由js和html组成,我们可以通过修改显示模板,然后将修改好的显示模板跟搜索结果绑定起来,来修改搜索结果的显示效果,例子如下图: 修改前 修改后 第 ...
- Java读写配置文件prop.properties
Java读写配置文件prop.properties @Test public void fun() throws IOException{ Properties prop=new Properties ...
- 20165219第4次实验《Android程序设计》实验报告
20165219第4次实验<Android程序设计>实验报告 一.实验内容及步骤 (一)Android Stuidio的安装Hello world测试 要求 参考http://www.cn ...