龙空技术网

动态编译 Java 代码以及生成 Jar 文件

SapphireCoder 229

前言:

眼前你们对“java程序生成jar”大体比较重视,大家都需要知道一些“java程序生成jar”的相关资讯。那么小编也在网络上收集了一些对于“java程序生成jar””的相关知识,希望大家能喜欢,各位老铁们快快来学习一下吧!

导读: 最近在看 Flink 源码的时候发现到一段实用的代码,该代码实现了 java 动态编译以及生成 jar 文件。将其进行改进后可以应用到我们的平台上,实现在平台页面上编写 java 代码语句,提交后由后台进行编译和打成 Jar 包再上传到指定的文件存储系统,从而代替之前在本地自己手动打 UDF 包的方式。下面我将对这段代码做一些简单分析,希望对各位有所帮助。

核心代码

public class TestUserClassLoaderJar {    private static final String GENERATED_UDF_CLASS = "LowerUDF";    private static final String GENERATED_UDF_CODE =            "public class " + GENERATED_UDF_CLASS + " extends extends org.apache.flink.table.functions.ScalarFunction {\n" +                    "  public String eval(String str) {\n" +                    "    return str.toLowerCase();\n" +                    "  }\n" +                    "}\n";    /**     * 将生成的 UDF class 打包到 JAR 中并且返回 JAR 所在的路径.     */    public static File createJarFile(File tmpDir, String jarName) throws IOException {        // 创建一个 java 文件        File javaFile = Paths.get(tmpDir.toString(), GENERATED_UDF_CLASS + ".java").toFile();        javaFile.createNewFile();        // 将代码写入 java 文件中        FileUtils.writeFileUtf8(javaFile, GENERATED_UDF_CODE);        // 编译 java文件生成 class 文件        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);        Iterable<? extends JavaFileObject> compilationUnit =                fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(javaFile));        JavaCompiler.CompilationTask task = compiler.getTask(                null,                fileManager,                diagnostics,                Collections.emptyList(),                null,                compilationUnit);        // 此处结果返回一个布尔值,可用于判断是否编译成功及是否执行下面的打包操作        task.call();         // 将 class 文件打包到 Jar 中        File classFile = Paths.get(tmpDir.toString(), GENERATED_UDF_CLASS + ".class").toFile();        File jarFile = Paths.get(tmpDir.toString(), jarName).toFile();        JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile));        JarEntry jarEntry = new JarEntry(GENERATED_UDF_CLASS + ".class");        jos.putNextEntry(jarEntry);        byte[] classBytes = FileUtils.readAllBytes(classFile.toPath());        jos.write(classBytes);        jos.closeEntry();        jos.close();        return jarFile;    }    public static void main(String[] args) throws IOException {        createJarFile(new File("G:\\jarSave\\"),"test.jar");    }}

以上代码主要完成以下三步操作:

创建一个 .java 文件,并将外部输入的 java 语句写入到该文件中对 java 文件进行编译并生成 class 文件将 class 文件打包到 JAR 中并返回 JAR 的路径

下图是动态编译的几个关键类的创建方式及作用:

JavaCompiler 的由来

在上面代码中通过 ToolProvider.getSystemJavaCompiler() 获取到 JavaCompiler。深入内部 findSystemToolClass() 方法发现其最终先是通过 System.getProperty("java.home") 获取到 /jdk1.8.0_241/jre 目录,再获取其上级目录中 lib 目录下的 tools.jar(也就是/jdk1.8.0_241/lib/ tools.jar),并进行动态加载 Jar 获取到 JavaCompiler。

findSystemToolClass 代码片段:

private Class<?> findSystemToolClass(String toolClassName)        throws MalformedURLException, ClassNotFoundException    {        // try loading class directly, in case tool is on the bootclasspath        try {            return Class.forName(toolClassName, false, null);        } catch (ClassNotFoundException e) {            trace(FINE, e);            // if tool not on bootclasspath, look in default tools location (tools.jar)            ClassLoader cl = (refToolClassLoader == null ? null : refToolClassLoader.get());            if (cl == null) {                File file = new File(System.getProperty("java.home"));                if (file.getName().equalsIgnoreCase("jre"))                    file = file.getParentFile();                for (String name : defaultToolsLocation)                    file = new File(file, name);                // if tools not found, no point in trying a URLClassLoader                // so rethrow the original exception.                if (!file.exists())                    throw e;                URL[] urls = { file.toURI().toURL() };                trace(FINE, urls[0].toString());                cl = URLClassLoader.newInstance(urls);                refToolClassLoader = new WeakReference<ClassLoader>(cl);            }            return Class.forName(toolClassName, false, cl);        }    }
补充:FileUtils 工具类(已删减,只保留所需部分)
public class FileUtils {    public static void writeFileUtf8(File file, String contents) throws IOException {        writeFile(file, contents, "UTF-8");    }    public static void writeFile(File file, String contents, String encoding) throws IOException {        byte[] bytes = contents.getBytes(encoding);        Files.write(file.toPath(), bytes, new OpenOption[]{StandardOpenOption.WRITE});    }    private static byte[] read(InputStream source, int initialSize) throws IOException {        int capacity = initialSize;        byte[] buf = new byte[initialSize];        int nread = 0;        while (true) {            int n;            while ((n = source.read(buf, nread, Math.min(capacity - nread, 4096))) > 0) {                nread += n;            }            if (n < 0 || (n = source.read()) < 0) {                return capacity == nread ? buf : Arrays.copyOf(buf, nread);            }            if (capacity <= 2147483639 - capacity) {                capacity = Math.max(capacity << 1, 4096);            } else {                if (capacity == 2147483639) {                    throw new OutOfMemoryError("Required array size too large");                }                capacity = 2147483639;            }            buf = Arrays.copyOf(buf, capacity);            buf[nread++] = (byte) n;        }    }    public static byte[] readAllBytes(Path path) throws IOException {        SeekableByteChannel channel = Files.newByteChannel(path);        Throwable var2 = null;        byte[] var7;        try {            InputStream in = Channels.newInputStream(channel);            Throwable var4 = null;            try {                long size = channel.size();                if (size > 2147483639L) {                    throw new OutOfMemoryError("Required array size too large");                }                var7 = read(in, (int) size);            } catch (Throwable var30) {                var4 = var30;                throw var30;            } finally {                if (in != null) {                    if (var4 != null) {                        try {                            in.close();                        } catch (Throwable var29) {                            var4.addSuppressed(var29);                        }                    } else {                        in.close();                    }                }            }        } catch (Throwable var32) {            var2 = var32;            throw var32;        } finally {            if (channel != null) {                if (var2 != null) {                    try {                        channel.close();                    } catch (Throwable var28) {                        var2.addSuppressed(var28);                    }                } else {                    channel.close();                }            }        }        return var7;    }}
最后

以上就是动态编译 Java 代码以及生成 Jar 文件的方式。

感谢您的阅读,如果喜欢本文欢迎关注和转发,本头条号将坚持持续分享IT技术知识。对于文章内容有其他想法或意见建议等,欢迎提出共同讨论共同进步。

标签: #java程序生成jar