【问题标题】:Adding jar file to instrumentation path将 jar 文件添加到检测路径
【发布时间】:2016-07-05 22:10:30
【问题描述】:

我有两个 jar 文件(例如,我们称它们为 Updater.jar 和 Code.jar)。 Updater.jar 使用其 main 方法启动,然后使用 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 http://stackoverflow.com/questions/10308221/how-to-copy-file-inside-jar-to-outside-the-jar
    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。
  • 只是通过修改java.library.path变量吗?
  • 我的意思是简单地将你需要的 jar 添加到类路径中,例如java -cp path/to/your.jar;path/to/other.jar com.example.app.MainApp。不确定这是否是您的意思?除此之外,如果您正在寻找更大的灵活性,您可以在运行时通过 URLClassLoader 类动态添加 JAR。
  • 我不能System.setProperty("java.library.path", System.getProperty("java.library.path" + File.separator + "JOSUser.jar");吗?或者这样做有什么不同?我更喜欢以编程方式而不是命令行来回答。
  • "java.library.path".jar 文件没有任何关系。这是加载native库的路径,见ClassLoader.findLibrary(String)

标签: java jar instrumentation bytecode-manipulation


【解决方案1】:

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 的类,则您必须改为使用 change the bootstrap path

请注意,这必须尽早发生:

Java™ 虚拟机规范 指定后续尝试解析 Java 虚拟机之前未成功尝试解析的符号引用总是会失败,并引发与以下原因相同的错误最初的解决尝试。因此,如果 JAR 文件包含与 Java 虚拟机尝试解析引用失败的类对应的条目,则后续解析该引用的尝试将失败,并出现与初始尝试相同的错误。

【讨论】:

  • 那么如果同名的不同类加载失败,这里的类也会加载吗?
  • 好吧,如果你将一个 jar 附加到一个类路径,你假设在这个范围内没有其他同名的类,否则,你的 jar 将永远不会被搜索。但是,如果在添加 jar 之前尝试使用此类加载器加载该类,则会记住失败。由于premain 在任何应用程序代码之前运行,这应该不是问题,只要您在执行任何可能导致加载尝试的操作之前附加 jar。
  • 启动了一个主应用程序。此应用程序启动第一个 jar(我们称之为 start.jar)。 start.jar 完成后,应用程序启动更新程序。然后更新程序自己启动 premain 方法。所以它确实在应用程序代码之前运行,这会有所不同吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-28
  • 2018-11-07
  • 2015-09-27
  • 1970-01-01
  • 2021-04-22
相关资源
最近更新 更多