• Java动态编译


    程序产生过程

    下图展示了从源代码到可运行程序的过程,正常情况下先编译(明文源码到字节码),后执行(JVM加载字节码,获得类模板,实例化,方法使用)。本文来探索下当程序已经开始执行,但在.class甚至.java还未就绪的情况下,程序如何获得指定的实现。这就是我们下面的主题,动态编译。
    程序流程图

    相关类介绍

    JavaCompiler: 负责读取源代码,编译诊断,输出class
    JavaFileObject: 文件抽象,代表源代码或者编译后的class
    JavaFileManager: 管理JavaFileObject,负责JavaFileObject的创建和保存位置
    ClassLoader: 根据字节码,生成类模板

    使用方式

    由于代码在编译的时候,类定义甚至类名称还不存在,所以没法直接声明使用的。只能定义一个接口代替之,具体实现留给后面的动态编译。

    public interface Printer {
        public void print();
    }

    源代码的文件级动态编译

    java源码以文件的形式存在本地,程序去指定路径加载源文件。

    String classPath = File2Class.class.getResource("/").getPath();
    //在这里我们是动态生成定义,然后写入文件。也可以直接读一个已经存在的文件
    String str = "import classloader.Printer;" 
        + "public class MyPrinter1 implements Printer {" 
        + "public void print() {" 
        + "System.out.println("test1");" 
        + "}}";
    FileWriter writer = new FileWriter(classPath + "MyPrinter1.java");
    writer.write(str);;
    writer.close();
    //获得系统编译器
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,null, null);
    //读入源文件
    Iterable fileObject = fileManager.getJavaFileObjects(classPath + "MyPrinter1.java");
    //编译
    JavaCompiler.CompilationTask task = compiler.getTask(
                    null, fileManager, null, null, null, fileObject);
    task.call();
    fileManager.close();
    //指定class路径,默认和源代码路径一致,加载class
    URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:" + classPath)});
    Printer printer = (Printer)classLoader.loadClass("MyPrinter1").newInstance();
    printer.print();

    源代码的内存级动态编译

    上节源代码落地了,这节让我们看下源代码和class全程在内存不落地,如何实现动态编译。思路是生成源代码对应的JavaFileObject时,从内存string读取;生成class对应的JavaFileObject时,以字节数组的形式存到内存。JavaFileObject是一个interface, SimpleJavaFileObject是JavaFileObject的一个基本实现,当自定义JavaFileObject时,继承SimpleJavaFileObject,然后改写部分函数。
    自定义JavaSourceFromString,作为源代码的抽象文件(来自JDK API文档)

    /**
     * A file object used to represent source coming from a string.
    */
    public class JavaSourceFromString extends SimpleJavaFileObject {
    /**
     * The source code of this "file".
     */
    final String code;
    /**
     * Constructs a new JavaSourceFromString.
     * @param name the name of the compilation unit represented by this file object
     * @param code the source code for the compilation unit represented by this file object
     */
    JavaSourceFromString(String name, String code) {
        super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
        this.code = code;
    }
    
    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        return code;
    }
    }
    

      

    JavaClassFileObject,代表class的文件抽象

    public class JavaClassFileObject extends SimpleJavaFileObject {
        //用于存储class字节
        ByteArrayOutputStream outputStream;
    
        public JavaClassFileObject(String className, Kind kind) {
            super(URI.create("string:///" + className.replace('.', '/') + kind.extension), kind);
            outputStream = new ByteArrayOutputStream();
        }
    
        @Override
        public OutputStream openOutputStream() throws IOException {
            return outputStream;
        }
    
        public byte[] getClassBytes() {
            return outputStream.toByteArray();
        }
    }
    

      

    ClassFileManager,修改JavaFileManager生成class的JavaFileObject的行为,另外返回一个自定义ClassLoader用于返回内存中的字节码对应的类模板

    public class ClassFileManager extends ForwardingJavaFileManager {
    
        private JavaClassFileObject classFileObject;
        /**
         * Creates a new instance of ForwardingJavaFileManager.
         *
         * @param fileManager delegate to this file manager
         */
        protected ClassFileManager(JavaFileManager fileManager) {
            super(fileManager);
        }
    
        /**
         * Gets a JavaFileObject file object for output
         * representing the specified class of the specified kind in the given location.
         */
        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, 
        FileObject sibling) throws IOException {
            classFileObject = new JavaClassFileObject(className, kind);
            return classFileObject;
        }
    
        @Override
        //获得一个定制ClassLoader,返回我们保存在内存的类
        public ClassLoader getClassLoader(Location location) {
            return new ClassLoader() {
                @Override
                protected Class<?> findClass(String name) throws ClassNotFoundException {
                    byte[] classBytes = classFileObject.getClassBytes();
                    return super.defineClass(name, classBytes, 0, classBytes.length);
                }
            };
        }
    }
    

      

    下面来偷梁换柱,用自定义的JavaFileObject/JavaFileManager来动态编译

    String str = "import Printer;" 
        + "public class MyPrinter2 implements Printer {" 
        + "public void print() {"
        + "System.out.println("test2");"
        + "}}";
    //生成源代码的JavaFileObject
    SimpleJavaFileObject fileObject = new JavaSourceFromString("MyPrinter2", str);
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    //被修改后的JavaFileManager
    JavaFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
    //执行编译
    JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, Arrays.asList(fileObject));
    task.call();
    //获得ClassLoader,加载class文件
    ClassLoader classLoader = fileManager.getClassLoader(null);
    Class printerClass = classLoader.loadClass("MyPrinter2");
    //获得实例
    Printer printer = (Printer) printerClass.newInstance();
    printer.print();
    

      

    参考

    http://docs.oracle.com/javase/7/docs/api/javax/tools/JavaCompiler.html
    http://www.cnblogs.com/flyoung2008/archive/2011/11/14/2249017.html

  • 相关阅读:
    kbmmw 5.14.00 发布
    关于C++ auto使用要注意的一点
    git设置socks5代理
    电子书分享网站
    spring cache相关
    intellij idea开启debug热加载/热部署
    git 多次commit合并成一次提交
    es feature一览
    数据中台
    Java Agent
  • 原文地址:https://www.cnblogs.com/whuqin/p/4981948.html
Copyright © 2020-2023  润新知