Getting the path to the directory of a given class

2019-07-04 09:21发布

问题:

I was confronted with code which tries to read some configuration files from the same directory where the .class file for the class itself is:

File[] configFiles = new File(
    this.getClass().getResource(".").getPath()).listFiles(new FilenameFilter() {
        public boolean accept(File dir, String name) {
            return name.endsWith(".xml");
        }
});

Apparently this works in some cases (when running the code inside Resin, perhaps), but for me, running Tomcat, it simply fails with NPE, because getClass().getResource(".") returns null.

A colleague suggested creating another config file containing a list of all the ".xml" config files (which indeed would work here as it stays quite static), and that you shouldn't really try to do something like this in Java.

Still, I'm wondering if there is some nice way, which works universally, for getting the path to the directory where a given .class file is located? I guess you could get it from the path of the .class file itself like this:

new File(this.getClass().getResource("MyClass.class").getPath()).getParent()

... but is this the only / cleanest way?

Edit: To clarify, assume we know this is used in an application deployed in such a way that MyClass.class will always be read from a .class file on disk, and the resources will be there in that same directory.

回答1:

I know this thread is old, but it's the top result in Google searches, and there were no satisfactory answers on here, for me. Here's some code I wrote, which works great for me. Of course there's the caveat that it may not have been loaded from disk, but it accounts for that, and returns null in that case. This works fine for finding the "container," that is, the root location of a class, be it a jar, or a folder. This may not suit your needs directly. If not, feel free to rip out the portions of the code that you do need.

/**
 * Returns the container url for this class. This varies based on whether or
 * not the class files are in a zip/jar or not, so this method standardizes
 * that. The method may return null, if the class is a dynamically generated
 * class (perhaps with asm, or a proxy class)
 *
 * @param c The class to find the container for
 * @return
 */
public static String GetClassContainer(Class c) {
    if (c == null) {
        throw new NullPointerException("The Class passed to this method may not be null");
    }
    try {
        while(c.isMemberClass() || c.isAnonymousClass()){
            c = c.getEnclosingClass(); //Get the actual enclosing file
        }
        if (c.getProtectionDomain().getCodeSource() == null) {
            //This is a proxy or other dynamically generated class, and has no physical container,
            //so just return null.
            return null;
        }
        String packageRoot;
        try {
            //This is the full path to THIS file, but we need to get the package root.
            String thisClass = c.getResource(c.getSimpleName() + ".class").toString();
            packageRoot = StringUtils.replaceLast(thisClass, Pattern.quote(c.getName().replaceAll("\\.", "/") + ".class"), "");
            if(packageRoot.endsWith("!/")){
                packageRoot = StringUtils.replaceLast(packageRoot, "!/", "");
            }
        } catch (Exception e) {
            //Hmm, ok, try this then
            packageRoot = c.getProtectionDomain().getCodeSource().getLocation().toString();
        }
        packageRoot = URLDecoder.decode(packageRoot, "UTF-8");
        return packageRoot;
    } catch (Exception e) {
        throw new RuntimeException("While interrogating " + c.getName() + ", an unexpected exception was thrown.", e);
    }
}


回答2:

What makes you assume that there's a class file on disk in its own directory?

A class could be:

  • Created entirely in memory
  • Loaded from a network connection
  • Loaded from a jar file

You can get the URL used to create the class itself, and if that starts with file:// then you could get the rest... but it won't work for all classes.



回答3:

If the resource is in the same folder as a .class file, it should be accessible via the classpath and can be loaded via getResourceAsStream directly.

this.getClass().getResourceAsStream( "filename.xml" )

As is posted earlier, classes themselves can be loaded remotely or in places where there's not a proper "path" (e.g. from a jarfile)



回答4:

I agree with your colleague that Java class loading was not designed to handle this use case. Sun Facelets uses a similar strategy of assuming URLs can be mapped to Files, and it's not pretty. I agree with Jon's comment that your getResource solution is probably the cleanest given your deployment assumptions. Since you asked if it was the only way, I'll also offer getClass().getProtectionDomain().getCodeSource().getLocation(), which should be the URL that the class loader actually loaded your class from (you would need to append the subdirectories for your class' package). This strategy also has the same URL-to-File assumptions, so it's no better in that regard. I can think of no other general solutions.

Note that getResource returns an encoded URL, which means that you should not use getPath() directly. In particular, spaces will causes issues, though this might not be an issue if you have control over your environment. Consider using new File(URL.toURI()).



回答5:

I think, you may be interested in PathMatchingResourcePatternResolver from the Spring Framework. You can use it directly in your code for navigating the configuration files or you can look for the implementation here.