特意写了个动态编译的测试代码如下:
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);
但是最后发现不经意间学到了更多更好的东西,还是记录下来吧