将jar文件添加到仪表路径

3

我有两个jar文件(为了举例方便,我们称它们为Updater.jar和Code.jar)。 Updater.jar 使用其主方法启动,然后再使用其premain方法启动自身:

package Update;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

public class InstructionLauncher {

    private List<UpdateInstruction> instructions = new ArrayList<UpdateInstruction>();
    private static InstructionLauncher instance;
    private Process process;

    public static InstructionLauncher initialise(){
        if(instance !=null) return instance;
        else return new InstructionLauncher();
    }

    public void registerPremain(UpdateInstruction inst){
        instructions.add(inst);
    }

    public void launchNext(){
        UpdateInstruction inst = instructions.get(0);
        String cls = inst.getClassName() + "." + inst.getMethodName();
        String[] args = new String[]{"java", "-javaagent", "JOSUpdater.jar", "-jar", inst.getClassName() + "." + inst.getMethodName()};
        ProcessBuilder builder = new ProcessBuilder(args);
        try {
            exportResource(cls, cls);
        } catch (Exception e) {
            UpdateManager.revert();
        }
        try {
            Process p = builder.start();
            process = p;
        } catch (IOException e) {
            e.printStackTrace();
        }
        while(!process.isAlive())launchNext();
    }

    private InstructionLauncher(){
        instance = this;
    }

    //From https://dev59.com/wWkv5IYBdhLWcg3w1kWr
    private String exportResource(String resourceName, String clazz) throws Exception {
        InputStream stream = null;
        OutputStream resStreamOut = null;
        String jarFolder;
        try {
            stream = Class.forName(clazz).getResourceAsStream(resourceName);//note that each / is a directory down in the "jar tree" been the jar the root of the tree
            if(stream == null) {
                throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
            }

            int readBytes;
            byte[] buffer = new byte[4096];
            jarFolder = new File(Class.forName(clazz).getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile().getPath().replace('\\', '/');
            resStreamOut = new FileOutputStream(jarFolder + resourceName);
            while ((readBytes = stream.read(buffer)) > 0) {
                resStreamOut.write(buffer, 0, readBytes);
            }
        } catch (Exception ex) {
            throw ex;
        } finally {
            stream.close();
            resStreamOut.close();
        }

        return jarFolder + resourceName;
    }

}

目前,premain方法如下所示:

package Update;

import java.lang.instrument.Instrumentation;

public class PremainLauncher {

    public static void premain(String args, Instrumentation inst){
        inst.addTransformer(new Transformer(), true);
        System.out.println("Registered instruction for package: " + args);
    }

}

我想知道如何将整个外部JAR(例如Code.jar)添加到检测路径中?
我知道Instrumentation.retransformClasses方法,但是要使用它,我需要获取该jar中所有类的List>,但我一直无法完成。
假设Code.jar有三个类文件:Main.class,writer.class和display.class。有没有办法获取每个类对象的列表,而不是它们的名称?

只需将所需的jar包添加到类路径中即可。 - ManoDestra
只需要修改java.library.path变量就可以了吗? - JD9999
我是指通过将所需的 jar 包添加到类路径中实现,例如 java -cp path/to/your.jar;path/to/other.jar com.example.app.MainApp。不确定这是否符合您的意图?除此之外,如果您正在寻找更大的灵活性,还可以通过 URLClassLoader 类在运行时动态添加 JAR 包。 - ManoDestra
我不能这样做 System.setProperty("java.library.path", System.getProperty("java.library.path" + File.separator + "JOSUser.jar"); 吗?还是说这样会有不同的效果?我更倾向于以编程的方式回答此问题,而不是使用命令行。 - JD9999
在那种情况下,URLClassLoader将做你所需的。 - ManoDestra
2
"java.library.path"与".jar"文件无关。这是用于加载本地库的路径,请参见ClassLoader.findLibrary(String) - Holger
1个回答

2

Java代理可以通过在启动方法中收到的 Instrumentation 接口来简单地添加jar文件,例如:

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class PremainLauncher {
    public static void premain(String args, Instrumentation inst) throws IOException{
        inst.appendToSystemClassLoaderSearch(new JarFile("Code.jar"));
        inst.addTransformer(new Transformer(), true);
        System.out.println("Registered instruction for package: " + args);
    }
}

请参考Instrumentation.appendToSystemClassLoaderSearch(…),如果您想以某种方式对JRE类进行检测,在检测过程中必须让经过检测的类能够访问Code.jar。否则,您需要改变引导路径,请参考更改引导路径
请注意,这个过程必须尽早完成:

Java™虚拟机规范规定,如果Java虚拟机在之前未能成功解析符号引用,并且此后再次尝试解析同一符号引用,则该尝试始终会以与初始解析尝试导致的错误相同的错误失败。因此,如果JAR文件包含与Java虚拟机曾经未能成功解析引用的类对应的条目,则随后对该引用的任何尝试都将以与初始尝试相同的错误失败。


那么如果同名的另一个类加载失败,这里的类也会失败吗? - JD9999
1
如果你将一个jar包附加到类路径中,那么你就假设在这个范围内没有其他同名的类,否则,你的jar包将永远不会被搜索到。但是,如果在你附加jar包之前尝试使用该类加载器加载该类,则失败将被记住。由于premain运行在任何应用程序代码之前,只要在执行可能导致加载尝试的任何操作之前附加jar包,这就不是问题。 - Holger
有一个主应用程序启动。该应用程序启动第一个jar文件(我们称其为start.jar)。当start.jar完成后,应用程序会启动更新程序。然后更新程序会启动自己的premain方法。因此,它会在应用程序代码之前运行,这会有所不同吗? - JD9999

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接