美文网首页
Java动态编译运行

Java动态编译运行

作者: 卫青臣 | 来源:发表于2021-03-15 11:07 被阅读0次

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

流程

1、获取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);
    }
}

2、编译文件

调用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命令查看

3、加载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;
}

4、使用加载后的类

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

/**
 * 调用类方法
 *
 * @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反射

    java编译与运行 编译:静态加载 如 new创建对象运行:动态加载 如 Class.forName()(得到...

  • Java动态编译运行

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

  • Java动态编译那些事

    jdk1.6中添加了编译API, 我们可以在java代码中调用编译API动态编译JAVA源文件, 也就是运行时编译...

  • 让Java代码动态运行

    背景 Java是编译型语言,它不能向JavaScript一样被动态执行,但有时我们却不得不让Java代码能动态运行...

  • 反射

    动态编译与静态编译 反射 一个注意的地方Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行...

  • kotlin的基础描述

    动态语言和静态语言动态语言即运行前不需要编译,在运行的时候边解释边运行。静态编译语言是在运行前需要编译,编译完成后...

  • logicJava的复习

    javac 编译:把Java的源文件 .java的文件编译成.class文件java 运行:运行.class的...

  • 反射&动态代理

    反射 Java 的动态性体现在:反射机制、动态执行脚本语言、动态操作字节码 反射:在运行时加载、探知、使用编译时未...

  • 动态编译

    动态编译应用场景:1、做一个浏览器端编写java代码,上传服务器编译和运行的在线评测系统。2、服务器动态加载某些类...

  • 动态执行和静态执行

    动态执行: 在运行期间根据动态信息来确定执行次序。 静态执行: Java虚拟机根据编译期间确定的执行次序顺序执行。

网友评论

      本文标题:Java动态编译运行

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