Java 往jar包更新或写入新文件(新增字节码类文件示例)

文章介绍了如何在Java中动态地向jar包中添加字节码文件,包括从字符串或输入流转换成字节码,以及使用JavaCompiler编译源代码为字节码。主要方法包括`JarTool`类的`addFileToJar`和`compileClass`,以及利用`JavaFileObject`进行编译。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.把java字节流数据或字符串数据写入到jar包中:

往jar包内新增java字节码文件,把java文件流转换为字符串:JarTool#addFileToJar(java.lang.String, java.lang.String, java.lang.String, java.lang.String)

字节流:JarTool#addFileToJar(java.lang.String, java.lang.String, java.lang.String, java.io.InputStream)


import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;


@UtilityClass
@Slf4j
public class JarTool {
    /**
     * 在上述代码中,我们使用 ClassLoader#getSystemClassLoader 方法获取系统类加载器,并使用 getResource 方法获取当前 jar 文件的 URL 对象。然后,使用 URL#getFile 方法获取 URL 对象所表示文件的路径字符串。如果是在 Windows 系统下,还需要判断路径字符串是否以 '/' 开头,并将其去掉。
     * <p>
     * 需要注意的是,在获取当前 jar 文件的路径时,需要确保该方法是在运行 jar 文件的 JVM 进程中执行的。如果该方法是在其他地方执行的(比如在 IDE 中运行该代码),可能会获取到不正确的路径。
     *
     * @return
     */
    @SneakyThrows public String getCurrentJarPath() {
        URL url = Thread.currentThread().getContextClassLoader().getResource("");
        String path = URLDecoder.decode(url.getFile(), "UTF-8");
        log.info("getCurrentJarPath path = " + path);
        //截取jar包物理路径:file:/E:/xxx/tools/zx/hg-projects/data-maps_1.0.1_20230523_sdev.jar!/BOOT-INF/classes!/
        path = path.replaceAll("file:/|!/BOOT-INF/classes!/", "");
        // 如果是在 Windows 系统下,还需要去掉路径前面的 '/'
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }
        return path;
    }

    /**
     * 在使用 JarFile 类的方法访问 jar 文件中的某个文件时,需要同时指定 jarFile 和 filePath 两个参数,以指明所要访问的文件的位置和名称。例如,在上一个示例中,读取 jar 文件中 "/myfolder/filename" 文件内容的代码可以如下编写:
     * <p>
     * java
     * JarFile jarFile = new JarFile("path/to/myjar.jar");
     * JarEntry entry = jarFile.getJarEntry("myfolder/filename");
     * InputStream in = jarFile.getInputStream(entry);
     * // ... 读取文件内容
     * 需要注意的是,filePath 参数应该是相对路径,而不是绝对路径。即使 jar 文件存在于本地文件系统的某个路径中,也不应该使用绝对路径来指定 filePath 参数。
     *
     * @param jarFile  jarFile 参数表示要操作的 jar 文件对象。在 Java 中,可以使用 java.util.jar.JarFile 类来表示一个 jar 文件,并通过该类提供的方法来访问 jar 文件中的内容。例如,可以通过 JarFile#getEntry 方法获取 jar 文件中的某个文件条目,或者通过 JarFile#getInputStream 方法获取 jar 文件中某个文件的输入流。
     * @param filePath filePath 参数则表示要操作的文件在 jar 文件中的相对路径。在 Java 中,jar 文件中的文件条目通常采用类似于文件系统中文件路径的方式来进行命名和组织,即使用斜杠 ('/') 分隔各级路径。例如,一个位于 jar 文件根目录下的文件的路径为 "/filename",一个位于 jar 文件的 "myfolder" 目录下的文件的路径为 "/myfolder/filename"。
     * @throws IOException
     */
    public void readFileFromJar(File jarFile, String filePath) throws IOException {
        try (JarFile jar = new JarFile(jarFile)) {
            // 获取 JarEntry 对象
            JarEntry entry = jar.getJarEntry(filePath);

            if (entry == null) {
                throw new FileNotFoundException("File not found: " + filePath);
            }

            // 读取文件内容
            try (InputStream in = jar.getInputStream(entry)) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = in.read(buffer)) != -1) {
                    System.out.write(buffer, 0, len);
                }
            }
        }
    }
    /**
     * 往jar包内新增java字节码文件
     *
     * @param jarFilePath    jar绝对路径:E:\xxx\project\zx\sz-customs\data-maps\target\data-maps_1.0.1_20230524_sdev.jar
     * @param srcPackagePath jar包内相对路径:BOOT-INF/classes/com/zx/maps/domain
     * @param className      类名:HeadManifestExam
     * @param data    :java类文件字符内容
     */
    @SneakyThrows public void addFileToJar(String jarFilePath, String srcPackagePath, String className, String data) {

        byte[] classBytes = ClassTool.compileClass(className, srcPackagePath, data);
        jarUpdate(jarFilePath, srcPackagePath, className, classBytes);
    }

    /**
     * 往jar包内新增java字节码文件
     *
     * @param jarFilePath    jar绝对路径:E:\xxx\project\zx\sz-customs\data-maps\target\data-maps_1.0.1_20230524_sdev.jar
     * @param srcPackagePath jar包内相对路径:BOOT-INF/classes/com/zx/maps/domain
     * @param className      类名:HeadManifestExam
     * @param inputStream    :java类文件流
     */
    public void addFileToJar(String jarFilePath, String srcPackagePath, String className, InputStream inputStream) {
        try {
            byte[] classBytes = null;
            if (inputStream instanceof FileInputStream) {
                // 如果是源代码文件流,则先编译为字节码文件流
                classBytes = ClassTool.compileClass(className, srcPackagePath, inputStream);
            } else if (inputStream instanceof ByteArrayInputStream) {
                // 如果是类文件流,则读取字节数据
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    bos.write(buffer, 0, bytesRead);
                }
                classBytes = bos.toByteArray();
                bos.close();
            }

            jarUpdate(jarFilePath, srcPackagePath, className, classBytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void jarUpdate(String jarFilePath, String srcPackagePath, String className, byte[] classBytes) throws IOException {
        JarFile jarFile = new JarFile(jarFilePath);

        String fileName = srcPackagePath + "/" + className + ".class";
        JarOutputStream target = new JarOutputStream(new FileOutputStream(jarFilePath + ".tmp"));

        byte[] buffer = new byte[1024];
        int bytesRead;
        boolean fileExists = false;
        // 复制原来的JarEntry到新的Jar文件中
        for (Enumeration<JarEntry> enumeration = jarFile.entries(); enumeration.hasMoreElements(); ) {
            JarEntry entry = enumeration.nextElement();
            if (entry.getName().equals(fileName)) {
                fileExists = true;
                continue;
            }
            InputStream entryStream = jarFile.getInputStream(entry);
            target.putNextEntry(entry);
            while ((bytesRead = entryStream.read(buffer)) != -1) {
                target.write(buffer, 0, bytesRead);
            }
            entryStream.close();
            target.closeEntry();
        }
        jarFile.close();

        if (!fileExists) {
            // 添加新的JarEntry到新的Jar文件中
            JarEntry entry = new JarEntry(fileName);
            target.putNextEntry(entry);
            target.write(classBytes);
            target.closeEntry();
        }

        target.close();

        File oldFile = new File(jarFilePath);
        oldFile.delete();
        File newFile = new File(jarFilePath + ".tmp");
        newFile.renameTo(oldFile);
    }

    public boolean compileToClass() {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        int result = compiler.run(null, null, null, new File("yourJavaFile.java").getPath());
        if (result == 0) {
            System.out.println("Compilation successful");
        } else {
            System.out.println("Compilation Failed");
        }
        return result == 0;
    }

}

2.编译java文件为字节码文件



import java.io.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.tools.*;

import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;


@UtilityClass
public class ClassTool {

    public byte[] compileClass(String className, String packagePath, InputStream sourceStream) throws IOException {
        // 根据需要调用编译器将源代码编译为字节码
        // 这里只是假设编译器已经正确安装,并编写了一个简单的编译方法
        // 实际上需要根据具体情况选用适当的编译工具
        String sourceCode = readString(sourceStream);
        byte[] bytes = null;
        try {
            bytes = compileWithJavac(className, packagePath, sourceCode);
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return bytes;
    }

    @SneakyThrows public byte[] compileClass(String className, String packagePath, String data) {
        return compileWithJavac(className, packagePath, data);
    }

    private static String readString(InputStream inputStream) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            bos.write(buffer, 0, bytesRead);
        }
        inputStream.close();
        return bos.toString("UTF-8");
    }

    private static byte[] compileWithJavac(String className, String packagePath, String sourceCode) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
        JavaFileObject file = new CharSequenceJavaFileObject(className, sourceCode);
        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
        List<String> optionList = new ArrayList<>();
        optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        boolean success = compiler.getTask(pw, fileManager, diagnostics, optionList, null, compilationUnits).call();

        if (!success) {
            for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
                System.err.println(diagnostic.getMessage(null));
            }
            throw new RuntimeException("Compilation failed");
        }
        byte[] bytes = getClassBytes(className, packagePath);
        fileManager.close();
        return bytes;
    }

    private static byte[] getClassBytes(String className, String packagePath) throws IOException {
        String classFileName = className + ".class";
        File file = new File(System.getProperty(packagePath), classFileName);
        FileInputStream inputStream = new FileInputStream(file);
        byte[] bytes = new byte[inputStream.available()];
        inputStream.read(bytes);
        inputStream.close();
        return bytes;
    }

    private static class CharSequenceJavaFileObject extends SimpleJavaFileObject {
        private CharSequence content;

        public CharSequenceJavaFileObject(String className, CharSequence content) {
            super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
            this.content = content;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return content;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值