Monday, November 14, 2011

Java: getResource() vs. getResource() ...er, huh?

The 'getResource' method in Java allows applications to get files and other resources generically, without hard-coded knowledge of the type of resource, as long as it's in your class path. The only caveat is that you make sure you understand the difference between the getResource method and the other method used for the same purpose called 'getResource'.

Er... wait. Huh? Is that right?

Unfortunately, yes.
Loading Resources

The Java programming language abstracts away the idea of filesystems and devices by allowing you to load data from “resources”. This is done by searching the class path. The class path is typically set at start-up from the CLASSPATH environment variable or the ‘-cp’ command line option, but it can also set programmatically at run-time.

When it comes time to actually load a resource, there are at least THREE different ways to do it. They all involve using a method called “getResource”, or “getResourceAsStream”. But despite the common name, the functionality is not identical.

The three ways to get a resource is Java are:
  1. ClassLoader.getResource(String name)
  2. ClassLoader.getSystemClassLoader().getResource(String name)
  3. Class.getResource(String name)

So, for example, code could find a resource in any of these ways. For example, let’s say I have a configuration file that is stored in the config directory of one of my jar files.

String myRsrc = “config/settings.xml”;
Class<?> C = this.getClass();
URL myResUrl;
myResUrl = C.getClassLoader().getResource(myRsrc);
myResUrl = C.getClassLoader().getSystemClassLoader().getResource(myRsrc);
myResUrl = C.getResource(myRsrc);
These methods look so much alike that it’s easy to assume that they all do the same thing. Then one might be tempted, as I was, to think “well, they all work the same, so I’ll just save some typing and use the last one because it’s the shortest”.

The next thing you know, you’re getting a NullPointerException because your getResource call couldn’t find your file and returned null. Then you spend 3 hours trying to figure out why some calls to getResource work, while others don’t, and why some paths work and others don’t, and why this kind of stupid crap always happens on a Friday afternoon at 3:25 and if you go home before figuring it out you’ll be pissy and stew about it all weekend!

The answer isn’t really all that complicated, but it can be confusing, given the identical name of the method calls. What’s easy to miss is that the Class.getResource method behaves differently from ClassLoader.getResource.

Example


The easiest way to see the difference is to use a simple example. Let’s say we have an entry in our classpath that points to a file structure called ‘src’:

src/
    top.txt
    com/acme/
        AcmeUtils.java
        config/
            settings.xml
This could be a directory, or it could be a jar file; it doesn’t matter. The point that matters is that ‘src’ is the entry in the class path, and will be considered the ‘top’ when that class path entry is being searched.

If I want to get top.txt from code in the AcmeUtils class, I can make one of these calls:

Class<?> C = this.getClass()
C.getClassLoader().getResource(“top.txt”)  // ok
C.getClassLoader().getSystemClassLoader().getResource(“top.txt”)  // ok
C.getClassLoader().getSystemResource(“top.txt”)  // ok
C.getClassLoader().getSystemResource(“/top.txt”)  // DOESN’T WORK
C.getResource(“top.txt”)  // DOESN’T WORK
It’s a good thing I can call the ClassLoader.getResource method in such a variety of ways. Too much consistency is boring, right? ;)

But what the heck is going on with the last one??? This really threw me for a loop when I ran into this, because the method name is the same, and I assumed that the behavior would be the same (I suppose that's what I get).

Now, let’s try the same thing with ‘settings.xml’:

ClassLoader cl = this.getClass().getClassLoader()
cl.getResource(“com/acme/config/settings.xml”)
cl.getSystemClassLoader().getResource(“com/acme/config/settings.xml”) 
cl.getSystemResource(“com/acme/config/settings.xml”)
cl.getSystemResource(“/com/acme/config/settings.xml”)  // DOESN’T WORK
this.getClass().getResource(“com/acme/config/settings.xml”)  // DOESN’T WORK
OK, well, at least it’s consistently inconsistent... But on the other hand, these two calls DO work:

this.getClass().getResource(“/com/acme/config/settings.xml”)
this.getClass().getResource(“/top.txt”)
And finally, the one that blew my mind:

this.getClass().getResource(“settings.xml”)  // ok. Huh!?!??!

Class.getResource


If I was a jerk, I’d tell you to RTFM, because the behavior is documented. But honestly, I don’t think the description is clear enough. Maybe it’s just me, but I read this thing 5 times, and it didn’t actually click until I experimented with it for a while.

When it comes down to it, it’s actually pretty simple. The getResource method in Class is a convenience method that ends up wrapping calls to ClassLoader.getResource. But it works in a way that’s supposed to make things easier.

The behavior makes sense if you think of the class like the current working directory in a command shell. A path with a leading ‘/’ is considered absolute, and your current directory has no bearing. However, with no leading ‘/’, the path is considered relative to your current directory. When you call ‘this.getClass().getResource()’, you are getting a resource relative to the current class.

So, thinking about it this way, the examples above make sense: from the AcmeUtils class, in the context of the ‘src’ class path entry, “/top.txt” is absolute and “settings.xml” is relative to the class.

Simple!

Conclusion


I’ll never get those 3 hours back that I spent agonizing over this, but I hope this hub will save someone else the trouble of figuring it out on their own.





1 comment:

Unknown said...

excellent piece of information, I had come to know about your website from my friend kishori, pune,i have read atleast 7 posts of yours by now, and let me tell you, your site gives the best and the most interesting information. This is just the kind of information that i had been looking for, i'm already your rss reader now and i would regularly watch out for the new posts, once again hats off to you! Thanx a lot once again, Regards, Difference Between Classpath and Path