现在的位置: 首页 > 综合 > 正文

实现Java代码在内存中编译执行

2018年02月03日 ⁄ 综合 ⁄ 共 3162字 ⁄ 字号 评论关闭

一般的java的动态编译是需要先生成java文件,然后编译成class,最后用classloader加载进来,生成最终的实例。 

我在这里介绍一种方法,不需要任何java类文件,将字符串输入到内存,然后编译,加载,执行,整个过程全部在内存中实现,
不会产生.java和.class文件,做到了洁净无污染。
环境:
jdk 1.6  
为什么是1.6?

早期的版本中(Java 5及以前版本)中只能通过tools.jar中的com.sun.tools.javac包来调用Java编译器,但由于tools.jar不是标准的Java库,在使用时必须要设置这个jar的路径。而在Java 6中为我们提供了标准的包来操作Java编译器,这就是javax.tools包。使用这个包,我们可以不用将jar文件路径添加到classpath中了。

思路:
1、常规做法,也是调用java编译器最简单的方法:生成  .java文件,编译生成  .class文件。

(1)通过ToolProvider类的静态方法getSystemJavaCompiler来得到一个JavaCompiler接口的实例。

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

(2)JavaCompiler中最核心的方法是run。通过这个方法可以编译java源程序。这个方法有3个固定参数和1个可变参数(可变参数是从Jave SE5开始提供的一个新的参数类型,用type… argu表示)。前3个参数分别用来为java编译器提供参数、得到Java编译器的输出信息以及接收编译器的错误信息,后面的可变参数可以传入一个或多个Java源程序文件。如果run编译成功,返回0。

int run(InputStream in, OutputStream out, OutputStream err, String... arguments)

如果前3个参数传入的是null,那么run方法将以标准的输入、输出代替,即System.in、System.out和System.err。如果我们要编译一个test.java文件,并将使用标准输入输出,run的使用方法如下:

int results = tool.run(null, null, null, "test.java");

很简单,但在Java 6中最好的方法是使用StandardJavaFileManager类。这个类可以很好地控制输入、输出,并且可以通过DiagnosticListener得到诊断信息,而DiagnosticCollector类就是listener的实现。

使用StandardJavaFileManager需要两步。首先建立一个DiagnosticCollector实例以及通过JavaCompiler的getStandardFileManager()方法得到一个StandardFileManager对象。最后通过CompilationTask中的call方法编译源程序。

2、我们要做的,就是不生成 .java文件,不生成 .class文件,一切都在内存中运行。
其实,很简单,只要在上述思路的基础上再加上一步,就一小步。
(1)我们可以编写一个 CharSequenceJavaFileObject extends SimpleJavaFileObject  ,通过这个类可以输入Java源代码字符串哦。

核心代码:
/**
     * @MethodName    : 编译java代码到Object
     * @Description    : TODO
     * @param fullClassName   类名
     * @param javaCode  类代码
     * @return Object
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public Object javaCodeToObject(String fullClassName, String javaCode) throws IllegalAccessException, InstantiationException {
        long start = System.currentTimeMillis(); //记录开始编译时间
        Object instance = null;
        //获取系统编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
        // 建立DiagnosticCollector对象
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        
         // 建立用于保存被编译文件名的对象
         // 每个文件被保存在一个从JavaFileObject继承的类中
        ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));
 
        List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
        jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));
 
        //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合
        List<String> options = new ArrayList<String>();
        options.add("-encoding");
        options.add("UTF-8");
        options.add("-classpath");
        options.add(this.classpath);
 
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
        // 编译源程序
        boolean success = task.call();
 
        if (success) {
            //如果编译成功,用类加载器加载该类
            JavaClassObject jco = fileManager.getJavaClassObject();
            DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
            Class clazz = dynamicClassLoader.loadClass(fullClassName,jco);
            instance = clazz.newInstance();
        } else {
            //如果想得到具体的编译错误,可以对Diagnostics进行扫描
            String error = "";
            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                error = error + compilePrint(diagnostic);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("javaCodeToObject use:"+(end-start)+"ms");
        return instance;
    }

点我代码打包下载。    从此以后,我的所有资源都免积分下载。

运行DynaCompTest的main方法即可。

什么动态生成.java文件,生成.class文件,都是浮云。一切都在内存中搞定,不用生成任何文件,做到零污染。

抱歉!评论已关闭.