读javac源码时奇怪com.sun.tools.javac.main.Main中有这么个boolean apiMode实例变量,代码注释说“如果apiMode为true,那么某些错误可能导致异常”。奇怪的是从命令行启动javac并没有相关代码能设置apiMode的值,于是在源码中翻来翻去,发现原来是动态编译时会用到apiMode这个变量,于是豁然开朗,还是记录下吧,好记性不如烂笔头
特意写了个动态编译的测试代码如下:
public static final String JAVA_FILE_NAME = "DynamicCompiler";
/**
* 动态编译javac入口:
* com.sun.tools.javac.api.JavacTaskImpl.call()
*/
@Test public void testDynamicCompiler() {
try {
// 动态编译
// compiler实际类型:com.sun.tools.javac.api.JavacTool
javax.tools.JavaCompiler compiler = javax.tools.ToolProvider.getSystemJavaCompiler();
// standardJavaFileManager实际类型 :com.sun.tools.javac.file.JavacFileManager
javax.tools.StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends javax.tools.JavaFileObject> iterable =
standardJavaFileManager.getJavaFileObjects(MainTest.JAVAFILES_PATH + "/" + JAVA_FILE_NAME + ".java");
// 相当于命令行调用javac时的参数
List<String> args = Arrays.asList("-d", MainTest.CLASSFILES_PATH);
// compilationTask实际类型:com.sun.tools.javac.api.JavacTaskImpl
javax.tools.JavaCompiler.CompilationTask compilationTask =
compiler.getTask(null, standardJavaFileManager, null, args, null, iterable);
// 调用com.sun.tools.javac.api.JavacTaskImpl.call(); 函数中会把apiMode设置为true
// 编译,调用com.sun.tools.javac.main.compile(String[], Context, List<JavaFileObject> ,Iterable<? extends Processor>)
compilationTask.call();
standardJavaFileManager.close();
// 用URLClassLoader来装载这个编译好的类
URL[] urls = new URL[] {new URL("file:/" + MainTest.CLASSFILES_PATH + "/")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class<?> clazz = urlClassLoader.loadClass(JAVA_FILE_NAME);
// 方法调用
Object obj = clazz.newInstance();
Method method = clazz.getMethod("sayHello");
method.invoke(obj);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| NoSuchMethodException | SecurityException | IllegalArgumentException
| InvocationTargetException | IOException e) {
e.printStackTrace();
}
}
被编译的java文件
public class DynamicCompiler {
public void sayHello() {
System.out.println("Hello");
}
}
以下javax.tools.ToolProvider.findSystemToolClass(String)方法中源码片段是关键,它会找到你机器%JAVA_HOME%/lib/tools.jar,然后装载
File file = new File(System.getProperty("java.home"));
if (file.getName().equalsIgnoreCase("jre"))
file = file.getParentFile();
for (String name : defaultToolsLocation)
file = new File(file, name);
// if tools not found, no point in trying a URLClassLoader
// so rethrow the original exception.
if (!file.exists())
throw e;
URL[] urls = { file.toURI().toURL() };
trace(FINE, urls[0].toString());
cl = URLClassLoader.newInstance(urls);
refToolClassLoader = new WeakReference<ClassLoader>(cl);
编译源码片段(来自com.sun.tools.javac.api.JavacTaskImpl):
public Boolean call() {
if (!used.getAndSet(true)) {
initContext();
notYetEntered = new HashMap<JavaFileObject, JCCompilationUnit>();
compilerMain.setAPIMode(true);
// 编译器入口 com.sun.tools.javac.main.Main.compile(String[], Context, List<JavaFileObject>,Iterable<? extends Processor>)
result = compilerMain.compile(args, context, fileObjects, processors);
cleanup();
return result == 0;
} else {
throw new IllegalStateException("multiple calls to method 'call'");
}
}
其实写了这么多,只有这一句才是我的最初目的
compilerMain.setAPIMode(true);
但是最后发现不经意间学到了更多更好的东西,还是记录下来吧