« Get Your Messaging On | Main | RELAX NG book in pre-publication »

Loading resources from the classpath

The suggested idiom in this blog post doesn't always work. The statement underneath:

I have not compiled the above, it is just an example.

is a clue :) It doesn't work because the system classloader can't always locate resources, particulary if they are buried inside a package structure and you have to refer to them using a path. As to why I'm not sure, but I ran into this problem yesterday at work. I have a solution, but since I don't fully understand it, I'm understandably uncomfortable :)

Some background - I have some code that uses an XML template to generate XML files - this is trivial stuff mind, I'm using MessageFormat to populate element content. I don't want this template to be easily altered, but I don't want it buried in a String either. So I put it in the template generator's package space and had it loaded in via the "Instance.class.getClassLoader()..." idiom. All the junit testing stuff around it worked, so I checked it in. But when a colleague deployed the code to JBoss as a jar, the resource wasn't to be found. I'm not sure why, but neither of these idioms worked:

  Instance.class.getClassLoader()...;
  ClassLoader.getSystemClassLoader()...;

I did some more testing and came the conclusion that to load a resource off the classpath you need to dynamically load a class packaged with said resource and use that to get the resource. For example, this worked where the above failed:

  Class c = Thread.currentThread()
    .getContextClassLoader().loadClass("...");
  c.getClass().getResourceAsStream(path);

A fuller example (using the flakier Class.forName):

  public class LE1150LoadResourceMain 
 {
    public static void main( String[] args )
      throws Exception  
   {
      Class c = Class.forName(
        "propylon.deps.generator.LE1150GeneratorImpl");
      InputStream is = c.getClass().getResourceAsStream(
        "/propylon/deps/generator/env-template.xml");
      StringBuffer buf = new StringBuffer();
      // load ....
      System.err.println(buf.toString());
    }
  }

works under a variety of loader environments that came to mind (IDE, JUnit, command line, J2EE containers, servlet containers).

Love to know why, if anyone does :) I suspect there's something simple and fundamental I'm not getting.

[alabama 3: woke up this morning]


October 4, 2003 03:28 PM

Comments

eu
(October 4, 2003 04:49 PM #)

Sure it is simple. Read the great summary at http://www.javaworld.com/javaqa/2003-08/01-qa-0808-property_p.html

Bill de hra
(October 4, 2003 05:19 PM #)

I know that article (and Vlad's classloader article is a favourite).

However, I'm not working with .properties files.

eu
(October 5, 2003 01:40 AM #)

What is the matter what are you loading from stream? The point is how to get to that URL or stream with a proper resource.

Bill de hra
(October 5, 2003 10:34 AM #)

Again, there's some things I should point out :)

I want to load an XML file, not a properties file. *Read* the article you're pointing me to and decide if you still think it's relevant.

I'd like to know why my working solution, does in fact, work. Vlad's article doesn't have any answers for me (tho' it's probably much like what I would do for a .properties case).

eu
(October 5, 2003 02:37 PM #)

Obviously you didn't got the point from Vlad's article. There are NO difference between xml or properties - they both are resources. In your code you make a few fundamental mistakes - using context class loader and system classloader. Instead you should of use current classloader, which used to load this instance. Think of resources the same way as of classes, they are been looked up exactly the same way.

Bill de hra
(October 5, 2003 09:48 PM #)

Sigh. Eugene, you'll just have to trust me when I say I get the point of Vlad's article, but a) it doesn't explain the problems I've had (what I'm really interested in), b) there's so much noise in there dedicated to .properties it's not that useful. My own tests suggest that ClassLoader.getSystemClassLoader() won't find a resource beginning with "/", but I'm not sure that explains everything I've seen in the last few days.

sigh
(October 6, 2003 02:27 AM #)

Bill, please enlighten myself and others and share your experience else I'm going to contine to side with eu.

James Strachan
(October 6, 2003 10:31 AM #)

Dunno if this helps Bill but, Class.forName() is evil...

http://radio.weblogs.com/0112098/stories/2003/02/12/classfornameIsEvil.html

Pawn
(October 6, 2003 03:01 PM #)

Bill, what's your deployment in JBoss look like. From what you wrote you mention that the resource you are trying to load is in a jar file. From where are you trying to access this resource from? The same jar or from another jar, war, or ear? Bear in mind that individual deployables be it a jar, war or ear will use their own classloader and resources cannot be shared between them without employing the facilities provided in the j2ee extention mechanism. I don't know the specifics of JBoss classloading architecture so if this is relevant then you may want to look into this as a possibility.

Bill de hra
(October 6, 2003 10:40 PM #)

James:
s'okay, I have a Thread.currentThread() load in there too... just being lazy ;)

Sigh (?):
take any side you want :) I'm not disputing Eugene's take on things, but as the same time I'm looking for an explanation so I do understand what's going on (otherwise it's voodoo) - it's not in that article, good as it may be. If it turns out that the problem with the app loader is simple the parth beginning with "/", I'm going to be annoyed at that!

Pawn:
Isolation: thanks, not something I'd thought of - I'll need to check how the code's being used to see if that's the case.

animaal
(October 8, 2003 10:57 AM #)

I wouldn't use the current thread's classloader to load the thing. Presumably the thread is in a pool that was created at appserver startup, and at that time, your application was unknown and unloaded. What I'd try is using the current class's classloader instead.

(Something like "this.getClass().getClassLoader().etc.etc" ??)

Ben
(October 28, 2003 10:31 PM #)

Bill,

FWIW, I use this idiom in servlet containers all the time:

Thread.currentThread().
getContextClassLoader().
getResourceAsStream("my/packages/here/file.xml");

and haven't had it fail on me yet.

this.getClass().
getResourceAsStream("/my/packages/here/file.xml");

worked when I tried it in Tomcat, although I've never used that particular idiom before.

As was noted elsewhere, delegating the request to the system class loader ain't going to work in a servlet container -- the system class loader is higher in the chain than the web app class loader.

I wouldn't worry about using the thread's class loader; the thread executing your applications code obviously is using a class loader that can load your application's code.

Good luck to you.

Trackback Pings

TrackBack URL for this entry:
http://www.dehora.net/mt/mt-tb.cgi/1096