Java实现运行时编译并动态调用
至于运行时编译有什么优势这里就不赘述了,直接进入主题:
JDK1.6提供了调用javac的接口。我们可以使用这个接口来实现运行时编译java文件,生成.class文件的目的。该类为javax.tools.JavaCompiler类。接下来使用自定义classloader加载,可实现动态调用。
首先,JavaCompiler使用方法如下:
public static int compile(String name){
//获得系统的JavaComiler实例(ToolProvider在javax.javax.tools包中)
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//定义一个以char字节的形式输出到控制台的字节流
OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
System.out.print((char)b);
}
};
//run方法第一个参数为java文件绝对路径(相对路径没试过)
//如果出错了,会将javac的信息输出到第三个参数的输出流
//如果返回值为0,说明编译成功
return compiler.run(null, null, out, name);
}
我们可以将编译好的文件使用classloader动态加载,并调用新编译的类。当然实现调用,必须要有一个公共接口。为了方便起见,我们将带编译放在该工程src/test目录下。工程目录如图所示:
其中MyTest工程中的Test文件将会在Complie运行时被Complie编译并加载
Test代码如下:
package test;
import commonface.DoSomeThing;
public class Test implements DoSomeThing{
@Override
public String doSomeThing() {
return "hello world";
}
}
双方的公共接口DoSomeThing:
package commonface;
public interface DoSomeThing {
public String doSomeThing();
}
最后是编译类Complie:
package complie;
import commonface.DoSomeThing;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Compile {
public static int compile(String name){
//注释见上文
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
System.out.print((char)b);
}
};
return compiler.run(null, out, out, name);
}
public static void run(String classPath) throws Exception{
//自定义一个ClassLoader,我们需要重载findclass
ClassLoader loader=new ClassLoader(){
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class cLass=null;
try {
//根据绝对路径找到文件
FileInputStream of=new FileInputStream(new File(className));
//一下几步将文件流变成byte数组
ByteArrayOutputStream out = new ByteArrayOutputStream(1000);
byte[] b = new byte[1000];
for (int n;(n = of.read(b)) != -1;) {
out.write(b, 0, n);
}
of.close();
byte[] bt=out.toByteArray();
//加载类信息
cLass= defineClass(bt, 0, bt.length);
out.close();
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
return cLass;
}};
//加载类
Class mc=loader.loadClass(classPath);
//强制转型,并调用,结果输出在控制台
DoSomeThing dt=(DoSomeThing)mc.newInstance();
System.out.println(dt.doSomeThing());
}
public static void main(String[] args) throws Exception{
if(compile(System.getProperty("user.dir")+"\\..\\MyTest\\src\\test\\Test.java")==0){
System.out.println("Done");
//javac编译的class文件在java文件同一目录下面
run(System.getProperty("user.dir")+"\\..\\MyTest\\src\\test\\Test.class");
} else {
System.out.println("Fail");
}
}
}
文中的Classloader类这里只是简单的提了一下。以后有机会再详细的说吧。另外,改程序在运行成功、如图所示:
成功的编译了Test文件,并调用了Test文件重载的doSomeThing函数,将结果输出到控制台中了。