首页 > 解决方案 > Edit and save other jar while running

问题描述

Currently I am trying to update an old project. The problem is, that in one of my sources (bungeecord) they have changed two fileds (see enum "protocol") from public final to final modifier. To make the project work again I need to access these two fields.

As a reason of this I try to "inject" the project. This works great, so the modifier changes but I am currently not able to save it to the jar file. But this is necessary.

The process of saving works perfectly for the "userconnection" (see enum below). In this case I edit a class modifier.

If you need any more Information please let me know.

When the "injection" (enum: protocol) is done and I check the modifier type of these fileds I see that there have been some changes. But when I restart the system and check the filed modifiers again before the "injection" they are as there were no changes.

public static int inject(InjectionType type) {
    try{

        System.out.println("Starting  injection.");
        System.out.println(type.getInfo());

        ClassPool cp = ClassPool.getDefault();
        CtClass clazz = cp.getCtClass(type.getClazz().getName());

        switch (type) {
            case USERCONNECTION:
                int modifier = UserConnection.class.getModifiers();
                if (!Modifier.isFinal(modifier) && Modifier.isPublic(modifier)) {
                    return -1;
                }
                clazz.setModifiers(Modifier.PUBLIC);
                break;
            case PROTOCOL:
                CtField field = clazz.getField("TO_CLIENT");
                field.setModifiers(Modifier.PUBLIC + Modifier.FINAL);
                field = clazz.getField("TO_SERVER");
                field.setModifiers(Modifier.PUBLIC + Modifier.FINAL);
                break;
            default:
                return -1;  //no data
        }

        ByteArrayOutputStream bout;
        DataOutputStream out = new DataOutputStream(bout = new ByteArrayOutputStream());
        clazz.getClassFile().write(out);

        InputStream[] streams = { new ByteArrayInputStream(bout.toByteArray()) };
        File bungee_file = new File(BungeeCord.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
        updateZipFile(bungee_file, type, streams);
        return 1;
    }catch (Exception e){
        e.printStackTrace();
    }
    return 0;
}

private static void updateZipFile(File zipFile, InjectionType type, InputStream[] ins) throws IOException {
    File tempFile = File.createTempFile(zipFile.getName(), null);
    if (!tempFile.delete()) {
        System.out.println("Warn: Cant delete temp file.");
    }
    if (tempFile.exists()) {
        System.out.println("Warn: Temp target file alredy exist!");
    }
    if (!zipFile.exists()) {
        throw new RuntimeException("Could not rename the file " + zipFile.getAbsolutePath() + " to " + tempFile.getAbsolutePath() + " (Src. not found!)");
    }
    int renameOk = zipFile.renameTo(tempFile) ? 1 : 0;
    if (renameOk == 0) {
        tempFile = new File(zipFile.toString() + ".copy");
        com.google.common.io.Files.copy(zipFile, tempFile);
        renameOk = 2;
        if (zipFile.delete()) {
            System.out.println("Warn: Src file cant delete.");
            renameOk = -1;
        }
    }
    if (renameOk == 0) {
        throw new RuntimeException("Could not rename the file " + zipFile.getAbsolutePath() + " to " + tempFile.getAbsolutePath() + " (Directory read only? (Temp:[R:" + (tempFile.canRead() ? 1 : 0) + ";W:" + (tempFile.canWrite() ? 1 : 0) + ",D:" + (tempFile.canExecute() ? 1 : 0) + "],Src:[R:" + (zipFile.canRead() ? 1 : 0) + ";W:" + (zipFile.canWrite() ? 1 : 0) + ",D:" + (zipFile.canExecute() ? 1 : 0) + "]))");
    }
    if (renameOk != 1) {
        System.out.println("Warn: Cant create temp file. Use .copy file");
    }
    byte[] buf = new byte[Configuration.getLoadingBufferSize()];
    System.out.println("Buffer size: " + buf.length);
    ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
    ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));

    ZipEntry entry = zin.getNextEntry();
    while (entry != null) {
        String path_name = entry.getName().replaceAll("/", "\\.");
        boolean notReplace = true;
        for (String f : type.getNames()) {
            if (f.equals(path_name)) {
                notReplace = false;
                break;
            }
        }
        if (notReplace) {
            out.putNextEntry(new ZipEntry(entry.getName()));
            int len;
            while ((len = zin.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        }
        entry = zin.getNextEntry();
    }
    zin.close();
    for (int i = 0; i < type.getNames().length; i++) {  
        InputStream in = ins[i];
        int index = type.getNames()[i].lastIndexOf('.');
        out.putNextEntry(new ZipEntry(type.getNames()[i].substring(0, index).replaceAll("\\.", "/") + type.getNames()[i].substring(index)));
        int len;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        out.closeEntry();
        in.close();
    }
    out.close();
    tempFile.delete();
    if (renameOk == -1) {
        System.exit(-1);
    }
}
}

@Getter
public enum InjectionType {
    USERCONNECTION(UserConnection.class, new String[] {"net.md_5.bungee.UserConnection.class"}, "Set modifiers for class UserConnection.class to \"public\""),
    PROTOCOL(Protocol.class, new String[] {"net.md_5.bungee.protocol.Protocol"}, "Set modifiers for class Protocol.class to \"public\"");

    private Class<?> clazz;
    private String[] names;
    private String info;


    InjectionType (Class<?> clazz, String[] names, String info) {
        this.clazz = clazz;
        this.names = names;
        this.info = info;
    }
}

标签: java

解决方案


当“注入”(枚举:协议)完成并检查这些文件的修饰符类型时,我发现有一些变化。但是当我重新启动系统并在“注入”之前再次检查提交的修饰符时,它们是没有变化的。

您要做的是使用 Java 反射永久修改 jar 文件中的字段访问权限。这不能工作,因为反射仅在运行时修改事物:

反射是一种 API,用于在运行时检查或修改方法、类、接口的行为。

摘自此页面

如果您希望更改是永久性的,您需要做的是对 jar 本身进行物理编辑。我知道您说过您无法做到这一点,但据我所知,这是唯一可能的方法。如果您希望在应用程序终止后保留更改并在程序启动之前应用更改,则必须对文件本身进行物理更改。

在此处阅读有关 Java 反射的官方文档。

但是,我真的不明白为什么在重新启动系统后更改仍然存在很重要。您需要更改访问权限的原因是您可以在运行时以某种方式访问​​并可能操作该类。您所做的是正确的,反射的更重要方面之一是操作数据,而无需实际修改物理文件本身并最终使用自定义分布。

编辑:阅读这个问题,它的评论和接受的答案。他们几乎说您无法编辑JVM当前正在使用的jar文件,它被锁定在只读状态。


推荐阅读