Java动态编译
程序产生过程
下图展示了从源代码到可运行程序的过程,正常情况下先编译(明文源码到字节码),后执行(JVM加载字节码,获得类模板,实例化,方法使用)。本文来探索下当程序已经开始执行,但在.class甚至.java还未就绪的情况下,程序如何获得指定的实现。这就是我们下面的主题,动态编译。
相关类介绍
JavaCompiler: 负责读取源代码,编译诊断,输出class
JavaFileObject: 文件抽象,代表源代码或者编译后的class
JavaFileManager: 管理JavaFileObject,负责JavaFileObject的创建和保存位置
ClassLoader: 根据字节码,生成类模板
使用方式
由于代码在编译的时候,类定义甚至类名称还不存在,所以没法直接声明使用的。只能定义一个接口代替之,具体实现留给后面的动态编译。
public interface Printer {
public void print();
}
源代码的文件级动态编译
java源码以文件的形式存在本地,程序去指定路径加载源文件。
String classPath = File2Class.class.getResource("/").getPath();
//在这里我们是动态生成定义,然后写入文件。也可以直接读一个已经存在的文件
String str = "import classloader.Printer;"
+ "public class MyPrinter1 implements Printer {"
+ "public void print() {"
+ "System.out.println(\"test1\");"
+ "}}";
FileWriter writer = new FileWriter(classPath + "MyPrinter1.java");
writer.write(str);;
writer.close();
//获得系统编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,null, null);
//读入源文件
Iterable fileObject = fileManager.getJavaFileObjects(classPath + "MyPrinter1.java");
//编译
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, null, null, null, fileObject);
task.call();
fileManager.close();
//指定class路径,默认和源代码路径一致,加载class
URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:" + classPath)});
Printer printer = (Printer)classLoader.loadClass("MyPrinter1").newInstance();
printer.print();
源代码的内存级动态编译
上节源代码落地了,这节让我们看下源代码和class全程在内存不落地,如何实现动态编译。思路是生成源代码对应的JavaFileObject时,从内存string读取;生成class对应的JavaFileObject时,以字节数组的形式存到内存。JavaFileObject是一个interface, SimpleJavaFileObject是JavaFileObject的一个基本实现,当自定义JavaFileObject时,继承SimpleJavaFileObject,然后改写部分函数。
自定义JavaSourceFromString,作为源代码的抽象文件(来自JDK API文档)
/**
* A file object used to represent source coming from a string.
*/
public class JavaSourceFromString extends SimpleJavaFileObject {
/**
* The source code of this "file".
*/
final String code;
/**
* Constructs a new JavaSourceFromString.
* @param name the name of the compilation unit represented by this file object
* @param code the source code for the compilation unit represented by this file object
*/
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
} @Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
JavaClassFileObject,代表class的文件抽象
public class JavaClassFileObject extends SimpleJavaFileObject {
//用于存储class字节
ByteArrayOutputStream outputStream;
public JavaClassFileObject(String className, Kind kind) {
super(URI.create("string:///" + className.replace('.', '/') + kind.extension), kind);
outputStream = new ByteArrayOutputStream();
}
@Override
public OutputStream openOutputStream() throws IOException {
return outputStream;
}
public byte[] getClassBytes() {
return outputStream.toByteArray();
}
}
ClassFileManager,修改JavaFileManager生成class的JavaFileObject的行为,另外返回一个自定义ClassLoader用于返回内存中的字节码对应的类模板
public class ClassFileManager extends ForwardingJavaFileManager {
private JavaClassFileObject classFileObject;
/**
* Creates a new instance of ForwardingJavaFileManager.
*
* @param fileManager delegate to this file manager
*/
protected ClassFileManager(JavaFileManager fileManager) {
super(fileManager);
}
/**
* Gets a JavaFileObject file object for output
* representing the specified class of the specified kind in the given location.
*/
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
classFileObject = new JavaClassFileObject(className, kind);
return classFileObject;
}
@Override
//获得一个定制ClassLoader,返回我们保存在内存的类
public ClassLoader getClassLoader(Location location) {
return new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = classFileObject.getClassBytes();
return super.defineClass(name, classBytes, 0, classBytes.length);
}
};
}
}
下面来偷梁换柱,用自定义的JavaFileObject/JavaFileManager来动态编译
String str = "import Printer;"
+ "public class MyPrinter2 implements Printer {"
+ "public void print() {"
+ "System.out.println(\"test2\");"
+ "}}";
//生成源代码的JavaFileObject
SimpleJavaFileObject fileObject = new JavaSourceFromString("MyPrinter2", str);
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//被修改后的JavaFileManager
JavaFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
//执行编译
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, Arrays.asList(fileObject));
task.call();
//获得ClassLoader,加载class文件
ClassLoader classLoader = fileManager.getClassLoader(null);
Class printerClass = classLoader.loadClass("MyPrinter2");
//获得实例
Printer printer = (Printer) printerClass.newInstance();
printer.print();
参考
http://docs.oracle.com/javase/7/docs/api/javax/tools/JavaCompiler.html
http://www.cnblogs.com/flyoung2008/archive/2011/11/14/2249017.html
Java动态编译的更多相关文章
- java动态编译笔记
1 前言 Java的动态编译知识,真真在实际开发中并不是经常遇到.但是学习java动态编译有助于我们从更深一层次去了解java.对掌握jdk的动态代理模式,这样我们在学习其他一些开源框架的时候就能够知 ...
- java动态编译 (java在线执行代码后端实现原理)(二)
在上一篇java动态编译 (java在线执行代码后端实现原理(一))文章中实现了 字符串编译成字节码,然后通过反射来运行代码的demo.这一篇文章提供一个如何防止死循环的代码占用cpu的问题. 思路: ...
- java动态编译 (java在线执行代码后端实现原理)
需求:要实现一个web网页中输入java代码,然后能知道编译结果以及执行结果 类似于菜鸟java在线工具的效果:https://c.runoob.com/compile/10 刚开始从什么概念都没有到 ...
- Java动态编译技术原理
这里介绍Java动态编译技术原理! 编译,一般来说就是将源代码转换成机器码的过程,比如在C语言中中,将C语言源代码编译成a.out,,但是在Java中的理解可能有点不同,编译指的是将java 源代码转 ...
- Java 动态编译--DynamicCompiler
java 动态编译自己写过程的机会比较少,记录一下: package com.xzlf.dynamicCompile; import java.io.IOException; import java. ...
- (转载)JAVA动态编译--字节代码的操纵
在一般的Java应用开发过程中,开发人员使用Java的方式比较简单.打开惯用的IDE,编写Java源代码,再利用IDE提供的功能直接运行Java 程序就可以了.这种开发模式背后的过程是:开发人员编写的 ...
- java动态编译类文件并加载到内存中
如果你想在动态编译并加载了class后,能够用hibernate的数据访问接口以面向对象的方式来操作该class类,请参考这篇博文-http://www.cnblogs.com/anai/p/4270 ...
- Java 动态编译组件 & 类动态加载
1.JDK6 动态编译组件 Java SE 6 之后自身集成了运行时编译的组件:javax.tools,存放在 tools.jar 包里,可以实现 Java 源代码编译,帮助扩展静态应用程序.该包中提 ...
- 动态代理 原理简析(java. 动态编译,动态代理)
动态代理: 1.动态编译 JavaCompiler.CompilationTask 动态编译想理解自己查API文档 2.反射被代理类 主要使用Method.invoke(Object o,Object ...
随机推荐
- NXP恩智浦P89V51RB2/RC2/RD2单片机解密芯片破解方法!
NXP恩智浦P89V51RB2/RC2/RD2单片机解密芯片破解 P89V51RB2/RC2/RD2是一款80C51微控制器,包含16/32/64kB Flash和1024字节的数据RAM. P89V ...
- WebGL入门教程(三)-webgl动画
前面文章: WebGL入门教程(一)-初识webgl WebGL入门教程(二)-webgl绘制三角形 WebGL动画有移动.旋转和缩放,我们将移动.旋转和缩放图形,然后将其绘制到屏幕上,称为变换(tr ...
- 使用Packet Sniffer抓包和分析(z-stack协议)
以下内容仅是自己学习总结,可能会有错误,有发现问题的欢迎指正(图片可以自己放大,还是比较清晰的). 1.协调器上电,其他设备均不上电,抓包如下: 通过观察可以发现,协调器建立网络成功后,会以15秒为周 ...
- mac linux 删除一个文件下边所有文件和文件夹
sudo rm -r -f 目录
- python基础01 Hello World!
摘要:简单的Hello Word! python 命令行 如已经安装python,那么在linux命令行中输入 $python 将进入python.乱吼在命令行提示符>>>后面输入 ...
- 使用canal分析binlog(一) 入门
canal介绍 canal是应阿里巴巴存在杭州和美国的双机房部署,存在跨机房同步的业务需求而提出的.早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求.不过早期的数据库同步 ...
- storm 入门原理介绍
1.hadoop有master与slave,Storm与之对应的节点是什么? 2.Storm控制节点上面运行一个后台程序被称之为什么? 3.Supervisor的作用是什么? 4.Topology与W ...
- 【Telerik】查询控件<telerik:RadMaskedTextBox>的使用
在SilverLight项目中,实现模糊查询,并将值绑定到列表中,使用了Telerik中的<telerik:RadMaskedTextBox>控件. 要先添加命名空间的引用: xmlns: ...
- jsp页面 如何通过el表达式获取request属性值
1. 我在一个超连接后加个参数如: http://localhost:8080/test/testjstl.jsp?pid=001 此时在jsp页面中,获取jsp传过来的pid的参数值 ...
- AmazeUI 框架知识点-组件
1.Badge 默认:添加 .am-badge class 到 <div> 或者 <span> 元素. 圆角:在默认样式的基础上添加 .am-radius class. 椭圆: ...