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动态编译那些事

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

  • Java的动态机制---动态编译

    Java的动态机制---动态编译1 作用 客户写代码,动态编译。 服务器动态加载某些类文件 2 两种做法 通过Ru...

  • java 动态编译

    为了更深入的了解动态代理,手动实现一下动态代理,其中涉及动态编译的东西,记录一下入门点推荐先看这个文章 Java ...

  • Java动态编译

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

  • java反射

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

  • 第一章1.1动态代理-JDK

    demo 验证原理,编译会动态生成一个java类,如下:

  • 访问者模式讨论篇:java的动态绑定与双分派

    访问者模式讨论篇:java的动态绑定与双分派 java的动态绑定 所谓的动态绑定就是指程执行期间(而不是在编译期间...

  • 【Java基础】动态编译

    源代码:https://gitee.com/AgentXiao/reflection动态编译要点:1、使用场景(在...

  • Java动态编译运行

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

  • 让Java代码动态运行

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

网友评论

    本文标题:Java动态编译

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