Java动态编译

写工具时遇到一个需求,程序跑起来之后,可以在程序上写代码并编译执行,这种情况就用到了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;
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容