java API的动态编译接口

     读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);

但是最后发现不经意间学到了更多更好的东西,还是记录下来吧

猜你喜欢

转载自budairenqin.iteye.com/blog/1539180