Search code examples
javaclassloaderbytecodeinstrumentationjavaagents

Location of javaagent jar in bootclasspath


I have a javaagent jar that I put on the bootclasspath using

Boot-Class-Path: myagent.jar

inside the MANIFEST.MF file.

I need to find out the directory on the filesystem in which the jar is located.

However the method described for this here doesnt seem to work for me:

 new File(MyClass.class.getProtectionDomain().getCodeSource().getLocation().toURI().g­etPath());

In this case the ProtectionDomain.getCodeSource() returns null. I guess this is happening because the jar has been put on the boot classpath. Because of this I also cannot do MyClass.getClassLoader() to get the resource location.

I am using Java 6.

Can anyone tell how to get the location of the jar?


Solution

  • You can use the System class loader to find classes on the boot class path. For example, this

    System.out.println(
      ClassLoader.getSystemClassLoader().getResource("java/lang/String.class")
    );
    

    Will print out something like,

    jar:file:/C:/Program%20Files/Java/jdk1.6.0_22/jre/lib/rt.jar!/java/lang/String.class
    

    To find the location of MyClass.class on disk, do

    String urlString = ClassLoader
     .getSystemClassLoader()
     .getResource("com/my/package/MyClass.class")
     .toString();
    
    urlString = urlString.substring(urlString.indexOf("file:"), urlString.indexOf('!'));
    URL url = new URL(urlString);
    File file = new File(url.toURI());
    System.out.println(file);
    System.out.println(file.exists());
    

    Update 2020-06-29 by kriegaex: Rather than writing a new answer to this old question, I want to enhance or update this very good answer, also taking account paths with spaces and Java 9+ modules.

    Here is a method returning the class file path as a File instance. If a class file is part of a JAR or Java runtime module (URL starts with protocol jrt:), it just returns the paths to the JAR or to the JMOD file, which is something you might not want, but it is just a showcase you can edit to your heart's content. Of course this is still hacky, e.g. not considering classes loaded from a web URL instead of from file, but you get the idea.

    public static File getFileForClass(String className) {
      className = className.replace('.', '/') + ".class";
      URL classURL = ClassLoader.getSystemClassLoader().getResource(className);
      if (classURL == null)
        return null;
      System.out.println("Class file URL: " + classURL);
    
      // Adapt this if you also have '.war' or other archive types
      if (classURL.toString().contains(".jar!")) {
        String jarFileName = classURL.getPath().replaceFirst("!.*", "");
        System.out.println("Containing JAR file: " + jarFileName);
        try {
          return new File(new URL(jarFileName).toURI());
        }
        catch (URISyntaxException | MalformedURLException e) {
          throw new RuntimeException(e);
        }
      }
    
      if (classURL.getProtocol().equals("jrt")) {
        String jrtModule = classURL.getFile().replaceFirst("/([^/]+).*", "$1");
        System.out.println("Target class is part of Java runtime module " + jrtModule);
        String jmodName = System.getProperty("java.home") + "/jmods/" + jrtModule + ".jmod";
        System.out.println("Containing Java module file: " + jmodName);
        return new File(jmodName);
      }
      try {
        return new File(classURL.toURI());
      }
      catch (URISyntaxException e) {
        throw new RuntimeException(e);
      }
    }
    

    When I call this from one of my IntelliJ IDEA projects with JDK 14 installed under a path containing spaces and deliberately adding a JAR also containing a path with spaces for testing, this code...

    public static void main(String[] args) throws IOException {
      Instrumentation instrumentation = ByteBuddyAgent.install();
      instrumentation.appendToSystemClassLoaderSearch(
        new JarFile("C:/Program Files/JetBrains/IntelliJ IDEA 2018.3/lib/idea_rt.jar")
      );
      Stream
        .of(
          Weaver.class.getName(),
          String.class.getName(),
          ByteBuddy.class.getName(),
          "com.intellij.execution.TestDiscoveryListener"
        )
        .forEach(className -> System.out.printf("Found file: %s%n%n", getFileForClass(className)));
    }
    

    ... yields this console output:

    Class file URL: file:/C:/Users/alexa/Documents/java-src/Sarek/sarek-aspect/target/classes/dev/sarek/agent/aspect/Weaver.class
    Found file: C:\Users\alexa\Documents\java-src\Sarek\sarek-aspect\target\classes\dev\sarek\agent\aspect\Weaver.class
    
    Class file URL: jrt:/java.base/java/lang/String.class
    Target class is part of Java runtime module java.base
    Containing Java module file: C:\Program Files\Java\jdk-14.0.1/jmods/java.base.jmod
    Found file: C:\Program Files\Java\jdk-14.0.1\jmods\java.base.jmod
    
    Class file URL: jar:file:/C:/Users/alexa/.m2/repository/net/bytebuddy/byte-buddy/1.10.13/byte-buddy-1.10.13.jar!/net/bytebuddy/ByteBuddy.class
    Containing JAR file: file:/C:/Users/alexa/.m2/repository/net/bytebuddy/byte-buddy/1.10.13/byte-buddy-1.10.13.jar
    Found file: C:\Users\alexa\.m2\repository\net\bytebuddy\byte-buddy\1.10.13\byte-buddy-1.10.13.jar
    
    Class file URL: jar:file:/C:/Program%20Files/JetBrains/IntelliJ%20IDEA%202018.3/lib/idea_rt.jar!/com/intellij/execution/TestDiscoveryListener.class
    Containing JAR file: file:/C:/Program%20Files/JetBrains/IntelliJ%20IDEA%202018.3/lib/idea_rt.jar
    Found file: C:\Program Files\JetBrains\IntelliJ IDEA 2018.3\lib\idea_rt.jar