Java动态编译

原创 2015年11月13日 16:14:02

程序产生过程

下图展示了从源代码到可运行程序的过程,正常情况下先编译(明文源码到字节码),后执行(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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/whuqin/article/details/49818309

Java动态创建类

Java可以创建动态类,学习看到,此处作为笔记。 代码如下: import java.io.IOException; import java.lang.reflect.Method; import...
  • liang_henry
  • liang_henry
  • 2016-01-04 17:12:25
  • 2083

Java_java动态编译整个项目

动态将java文件编译为class文件解决方案: 将temp\sdl\src目录中的java源文件编译成class文件,并存放到temp\sdl\classes目录中 java中早就提供了用j...
  • xiaozaq
  • xiaozaq
  • 2017-01-11 22:24:29
  • 718

Java File,object,byte[]间转换

1. import java.io.BufferedOutputStream; 2. import java.io.ByteArrayInputStream; 3. impor...
  • Jinliang_890905
  • Jinliang_890905
  • 2011-12-06 23:35:11
  • 1946

Java 运行时动态编译源代码原理和实现

编译,一般来说就是将源代码转换成机器码的过程,比如在C语言中中,将C语言源代码编译成a.out,,但是在Java中的理解可能有点不同,编译指的是将java 源代码转换成class字节码的过程,而不是真...
  • lmy86263
  • lmy86263
  • 2017-03-02 23:08:37
  • 3274

FileManager类的设计与实现

在学习java之初就一直想着要自己封装一个文件管理的类,可以实现各种文件操作。但是一直迟迟没有实现,然后我就作为一个“任务”给了小猫,让小猫去学习一些文件操作相关的知识,其实小猫挺认真的,并且也挺聪明...
  • tsyj810883979
  • tsyj810883979
  • 2011-04-17 23:23:00
  • 3851

Java动态编译执行

在某些情况下,我们可能需要动态生成java代码,通过动态编译,然后执行代码。JAVA API提供了相应的工具(JavaCompiler)来实现动态编译。下面我们通过一个简单的例子介绍,如何通过Java...
  • zleven
  • zleven
  • 2017-01-05 16:18:02
  • 5174

Java学习之-动态编译-DynamicCompile_反射调用

一、动态编译 JAVA6.0引入了动态编译机制。动态编译的应用场景:      可以做一个浏览器端编写java代码,上传服务器编译和运行的在线评测系统,需要进行安全检查。     服务器动...
  • haitaofeiyang
  • haitaofeiyang
  • 2015-03-14 22:37:48
  • 5643

java 动态编译.java文件,动态运行类

  • 2010年04月05日 01:31
  • 1.29MB
  • 下载

动态编译和运行外部java文件

笔者在最近的项目中对一个用户任意指定的Java项目或Java文件进行测试,这就涉及到编译和运行这些Java文件,折腾一段时间后实现了这个功能,在这记录下使用到的技术点。编译Java文件对于一个给定的j...
  • u012465296
  • u012465296
  • 2016-08-08 17:40:39
  • 1467

Java之动态编译,静态编译简单理解和实例

开心一笑【年底是各种案件的高发期,我们去ATM取钱的时候,一定要注意遮挡,不要被陌生人看到你的余额,要不然啊,就,,,非常容易被人嘲笑。其实对于胖子来说,买衣服最简单了,都不用进去。直接就在门口问一句...
  • huangwenyi1010
  • huangwenyi1010
  • 2017-01-07 09:44:25
  • 2484
收藏助手
不良信息举报
您举报文章:Java动态编译
举报原因:
原因补充:

(最多只允许输入30个字)