How to get classpath from classloader?

徘徊边缘 提交于 2019-11-28 03:39:32
Adel Boutros

If the classloader uses URLs, it must be a URLClassloader. What you have access to is the URLs which defines the classpath for it along side with its parent ClassLoader.

To get the URLs, simply do the following:

((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs()

UPDATE: My original answer below is woefully inadequate, now that I have spent three years developing FastClasspathScanner, and have had a large number of bug reports filed about certain classpath environments not working with this library. FastClasspathScanner now handles numerous complex classpath specification mechanisms. Even just finding the classpath can be insanely complicated in the general case (let alone scanning it), since there are so many ways to add jars and directories to the classpath.

For one thing, the code I gave below only handles URLClassLoader, and a lot of major runtime environments and containers don't extend this, they implement their own classloader from scratch. But it gets much more complicated than this in the case of Java 9+, since even though the traditional classpath still exists, everything in the future will be moving towards using the module path, not the classpath. Modules have URLs, but they are "jrt:/" URLs, not "file:/" URLs, and module URLs don't actually include a file path, only the module name -- so you can't even in general locate the module on disk. Your only option is to use the (heavily encapsulated) module system to work with modules. I wrote about how to scan the module path here.

FastClasspathScanner handles numerous complex classpath specification mechanisms, so you don't need to reinvent the wheel. You can fetch a list of classpath entries from FastClasspathScanner -- this will save you the trouble of trying to get something working with all the diverse classpath specification mechanisms you find out in the wild. (Apologies if that last link breaks -- the API and docs for FCS will be changing soon.)

--

[Old answer -- obsolete:]

The other answers are correct in most situations, but it gets more complicated than that in some settings: e.g. Maven, Tomcat and JUnit have their own classpath support and they don't use the system classpath.

So far this is the most complete system I have managed to come up with (this code is from my Fast Classpath Scanner project):

/** The unique elements of the classpath, as an ordered list. */
private final ArrayList<File> classpathElements = new ArrayList<>();

/** The unique elements of the classpath, as a set. */
private final HashSet<String> classpathElementsSet = new HashSet<>();

/** Clear the classpath. */
private void clearClasspath() {
    classpathElements.clear();
    classpathElementsSet.clear();
}

/** Add a classpath element. */
private void addClasspathElement(String pathElement) {
    if (classpathElementsSet.add(pathElement)) {
        final File file = new File(pathElement);
        if (file.exists()) {
            classpathElements.add(file);
        }
    }
}

/** Parse the system classpath. */
private void parseSystemClasspath() {
    // Look for all unique classloaders.
    // Keep them in an order that (hopefully) reflects the order in which class resolution occurs.
    ArrayList<ClassLoader> classLoaders = new ArrayList<>();
    HashSet<ClassLoader> classLoadersSet = new HashSet<>();
    classLoadersSet.add(ClassLoader.getSystemClassLoader());
    classLoaders.add(ClassLoader.getSystemClassLoader());
    if (classLoadersSet.add(Thread.currentThread().getContextClassLoader())) {
        classLoaders.add(Thread.currentThread().getContextClassLoader());
    }
    // Dirty method for looking for any other classloaders on the call stack
    try {
        // Generate stacktrace
        throw new Exception();
    } catch (Exception e) {
        StackTraceElement[] stacktrace = e.getStackTrace();
        for (StackTraceElement elt : stacktrace) {
            try {
                ClassLoader cl = Class.forName(elt.getClassName()).getClassLoader();
                if (classLoadersSet.add(cl)) {
                    classLoaders.add(cl);
                }
            } catch (ClassNotFoundException e1) {
            }
        }
    }

    // Get file paths for URLs of each classloader.
    clearClasspath();
    for (ClassLoader cl : classLoaders) {
        if (cl != null) {
            for (URL url : ((URLClassLoader) cl).getURLs()) {
                if ("file".equals(url.getProtocol())) {
                    addClasspathElement(url.getFile());
                }
            }
        }
    }
}

/** Override the system classpath with a custom classpath to search. */
public FastClasspathScanner overrideClasspath(String classpath) {
    clearClasspath();
    for (String pathElement : classpath.split(File.pathSeparator)) {
        addClasspathElement(pathElement);
    }
    return this;
}

/**
 * Get a list of unique elements on the classpath (directories and files) as File objects, preserving order.
 * Classpath elements that do not exist are not included in the list.
 */
public ArrayList<File> getUniqueClasspathElements() {
    return classpathElements;
}

For future reference, in case you need to pass in the class path to ProcessBuilder:

StringBuffer buffer = new StringBuffer();
for (URL url :
    ((URLClassLoader) (Thread.currentThread()
        .getContextClassLoader())).getURLs()) {
  buffer.append(new File(url.getPath()));
  buffer.append(System.getProperty("path.separator"));
}
String classpath = buffer.toString();
int toIndex = classpath
    .lastIndexOf(System.getProperty("path.separator"));
classpath = classpath.substring(0, toIndex);
ProcessBuilder builder = new ProcessBuilder("java",
    "-classpath", classpath, "com.a.b.c.TestProgram");

In case other answers don't work, try this:

ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url: urls) {
    System.out.println(url.getFile());
}

Drop this code into an empty jsp page to view classLoader hierarchy and associated jars loaded at each level.

visit() method below could also be used on its own

<%!
    public void visit(StringBuilder sb, int indent, ClassLoader classLoader) {
        if (indent > 20 || classLoader == null)
            return;
        String indentStr = new String(new char[indent]).replace("\0", "    ");
        sb.append("\n");
        sb.append(indentStr);
        sb.append(classLoader.getClass().getName());
        sb.append(":");
        if (classLoader instanceof java.net.URLClassLoader) {
            java.net.URL[] urls = ((java.net.URLClassLoader)classLoader).getURLs();
            for (java.net.URL url : urls) {
                sb.append("\n");
                sb.append(indentStr);
                sb.append(url);
            }
        }
        sb.append("\n");
        visit(sb, indent + 1, classLoader.getParent());
    }

%>

<%
StringBuilder sb = new StringBuilder();
visit(sb,1,this.getClass().getClassLoader());
%>
<pre>
<%=sb%>
</pre>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!