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