Java动态编译

作者: 老鼠AI大米_Java全栈 | 来源:发表于2022-12-29 11:11 被阅读0次

    写工具时遇到一个需求,程序跑起来之后,可以在程序上写代码并编译执行,这种情况就用到了Java动态编译运行

    流程

    获取JavaCompiler

    获取JavaCompiler需要用到jdk的tools包,如果只有jre,就需要手动把tools包放到JAVA_HOME的lib目录下

    private static JavaCompiler compiler;
    
    static {
        compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            String error = String.format("Java运行环境缺少文件:请将'系统jdk目录\\lib\\tools.jar'文件复制到'%s\\lib\\目录下'", System.getProperty("java.home"));
            System.out.println("ClassUtil init: " + error);
            throw new RuntimeException(error);
        }
    }
    

    编译文件

    调用JavaCompiler的run方法,即可编译Java文件,run方法接收一个输入流,两个输出流和若干个字符串参数,源码注释如下:

    /**
     * Run the tool with the given I/O channels and arguments. By
     * convention a tool returns 0 for success and nonzero for errors.
     * Any diagnostics generated will be written to either {@code out}
     * or {@code err} in some unspecified format.
     *
     * @param in "standard" input; use System.in if null
     * @param out "standard" output; use System.out if null
     * @param err "standard" error; use System.err if null
     * @param arguments arguments to pass to the tool
     * @return 0 for success; nonzero otherwise
     * @throws NullPointerException if the array of arguments contains
     * any {@code null} elements.
     */
    int run(InputStream in, OutputStream out, OutputStream err, String... arguments);
    
    • 其中in输入流是运行起来后的输入,默认为null,运行时不需要输入;
    • output输出流控制运行信息的输出,默认为null,信息会打印到控制台,也可以自定义一个输出流自定义输入,比如输出到指定的日志文件;
    • error输入流和output类型,区别在于error只输出错误级别的信息;
    • 最后的字符串参数则是控制编译的参数,即javac命令的参数,比如:
      -d指定放置生成的类文件的位置
      -s指定放置生成的源文件的位置
      这些参数可以在命令指示符中输入javac命令查看

    加载class文件

    上面的编译步骤完成后,会输出class文件,想要将class运行起来,要先加载文件
    使用ClassLoader来加载class文件

    //编译的类文件路径
    public static final String CLASS_PATH = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes";
    
    static class MyClassLoader extends ClassLoader { 
        
        @Override
        protected Class<?> findClass(String name) {
            String myPath = "file:///" + CLASS_PATH.replaceAll("\\\\", "/") + "/" + name.replace(".", "/") + ".class";
            byte[] cLassBytes = null;
            try {
                Path path = Paths.get(new URI(myPath));
                cLassBytes = Files.readAllBytes(path);
            } catch (IOException | URISyntaxException e) {
                System.out.println(e);
            }
            return defineClass(name, cLassBytes, 0, cLassBytes.length);
        }
    }
    
    private static Class<?> load(String name) {
        //加载类文件的方法,返回加载后的Class
        Class<?> cls = null;
        try {
            //这里使用自定义的ClassLoader
            ClassLoader classLoader = new MyClassLoader();
        } catch (Exception e) {
            System.out.println(e);
        }
        return cls;
    }
    

    使用加载后的类

    将类文件加载后,就可以使用了,通过反射机制,获取类的方法并调用

    /**
     * 调用类方法
     *
     * @param cls        类
     * @param methodName 方法名
     * @param paramsCls  方法参数类型
     * @param params     方法参数
     * @return
     */
    public static Object invoke(Class<?> cls, String methodName, Class<?>[] paramsCls, Object[] params)
            throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Method method = cls.getDeclaredMethod(methodName, paramsCls);
        Object obj = cls.newInstance();
        return method.invoke(obj, params);
    }
    
    public static void main(String[] args) {
        Class<?> cls = load("com.xxx.xxx");
        //调用加载后Class的方法
        invoke(cls, methodName, paramsCls, params);
    }
    

    完整代码

    Logger是自定义的日志类

    package com.xxx.utils;
    
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class ClassUtil {
        public static final String CLASS_PATH = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes";
        private static JavaCompiler compiler;
    
        static {
            compiler = ToolProvider.getSystemJavaCompiler();
            if (compiler == null) {
                String error = String.format("Java运行环境缺少文件:请将'系统jdk目录\\lib\\tools.jar'文件复制到'%s\\lib\\目录下'", System.getProperty("java.home"));
                Logger.e("ClassUtil init", error);
                throw new RuntimeException(error);
            }
        }
    
        static class MyClassLoader extends ClassLoader {
            @Override
            protected Class<?> findClass(String name) {
                String myPath = "file:///" + CLASS_PATH.replaceAll("\\\\", "/") +
                        "/" + name.replace(".", "/") + ".class";
                byte[] cLassBytes = null;
                try {
                    Path path = Paths.get(new URI(myPath));
                    cLassBytes = Files.readAllBytes(path);
                } catch (IOException | URISyntaxException e) {
                    Logger.e(e);
                }
                return defineClass(name, cLassBytes, 0, cLassBytes.length);
            }
        }
    
        public static Object execute(ExecuteOptions options)
                throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
            Logger.i("java file path: " + options.compilerOptions.srcPath);
            compiler(options.compilerOptions);
            Class<?> cls = load(String.format("%s.%s", options.pkgName, options.clzName));
            return invoke(cls, options.methodName, options.paramsCls, options.params);
        }
    
        public static int compiler(CompilerOptions options) {
            checkCompiler();
            return compiler.run(options.in, options.out, options.error, "-d", options.targetPath, options.srcPath);
        }
    
        /**
         * 加载类
         *
         * @param name 类名
         * @return
         */
        private static Class<?> load(String name) {
            Class<?> cls = null;
            try {
                ClassLoader classLoader = new MyClassLoader();
                //classLoader = ClassUtil.class.getClassLoader();
                cls = classLoader.loadClass(name);
                Logger.d("Load Class[" + name + "] by " + classLoader);
            } catch (Exception e) {
                Logger.e(e);
            }
            return cls;
        }
    
        /**
         * 调用类方法
         *
         * @param cls        类
         * @param methodName 方法名
         * @param paramsCls  方法参数类型
         * @param params     方法参数
         * @return
         */
        public static Object invoke(Class<?> cls, String methodName, Class<?>[] paramsCls, Object[] params)
                throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
            Method method = cls.getDeclaredMethod(methodName, paramsCls);
            Object obj = cls.newInstance();
            return method.invoke(obj, params);
        }
    
        private static void checkCompiler() {
            if (compiler == null) {
                compiler = ToolProvider.getSystemJavaCompiler();
                if (compiler == null) {
                    String error = String.format("Java运行环境缺少文件:请将'系统jdk目录\\lib\\tools.jar'文件复制到'%s\\lib\\目录下'", System.getProperty("java.home"));
                    Logger.e("ClassUtil init", error);
                    throw new RuntimeException(error);
                }
            }
        }
    
        /**
         * 执行参数
         */
        public static class ExecuteOptions{
            public CompilerOptions compilerOptions;
            public String pkgName;
            public String clzName;
            public String methodName;
            public Class<?>[] paramsCls;
            public Object[] params;
    
            public ExecuteOptions() {
                super();
            }
    
            public ExecuteOptions(CompilerOptions compilerOptions, String pkgName, String clzName, String methodName, Class<?>[] paramsCls, Object[] params) {
                this.compilerOptions = compilerOptions;
                this.pkgName = pkgName;
                this.clzName = clzName;
                this.methodName = methodName;
                this.paramsCls = paramsCls;
                this.params = params;
            }
    
            public ExecuteOptions setCompilerOptions(CompilerOptions compilerOptions) {
                this.compilerOptions = compilerOptions;
                return this;
            }
    
            public ExecuteOptions setPkgName(String pkgName) {
                this.pkgName = pkgName;
                return this;
            }
    
            public ExecuteOptions setClzName(String clzName) {
                this.clzName = clzName;
                return this;
            }
    
            public ExecuteOptions setMethodName(String methodName) {
                this.methodName = methodName;
                return this;
            }
    
            public ExecuteOptions setParamsCls(Class<?>[] paramsCls) {
                this.paramsCls = paramsCls;
                return this;
            }
    
            public ExecuteOptions setParams(Object[] params) {
                this.params = params;
                return this;
            }
        }
    
        /**
         * 编译参数
         */
        public static class CompilerOptions {
            public InputStream in;
            public OutputStream out;
            public OutputStream error;
            public String targetPath = CLASS_PATH;
            public String srcPath;
    
            public CompilerOptions() {
                super();
            }
    
            public CompilerOptions(String targetPath, String srcPath) {
                this.targetPath = targetPath;
                this.srcPath = srcPath;
            }
    
            public CompilerOptions(InputStream in, OutputStream out, OutputStream error, String targetPath, String srcPath) {
                this.in = in;
                this.out = out;
                this.error = error;
                this.targetPath = targetPath;
                this.srcPath = srcPath;
            }
    
            public CompilerOptions setIn(InputStream in) {
                this.in = in;
                return this;
            }
    
            public CompilerOptions setOut(OutputStream out) {
                this.out = out;
                return this;
            }
    
            public CompilerOptions setError(OutputStream error) {
                this.error = error;
                return this;
            }
    
            public CompilerOptions setTargetPath(String targetPath) {
                this.targetPath = targetPath;
                return this;
            }
    
            public CompilerOptions setSrcPath(String srcPath) {
                this.srcPath = srcPath;
                return this;
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:Java动态编译

        本文链接:https://www.haomeiwen.com/subject/dazaqdtx.html