osgiclassloaderexecutorservicejbehaverft

ClassLoader finds Resource only in specific Threads


I am struggling with a situation in which a ClassLoader is trying to resolve a resource, which only works under certain conditions.

The use-case is as follows: I am using IBM Rational Functional Tester in combination with JBehave for automated acceptance tests. JBehave specifies the tests as plain text story files. These story files can refer to other story files, so called "Given Stories". JBehave uses the ExecutorService to execute stories potentially multi-threaded. While JBehave has no problems to load the text files (with ClassLoader.getResourceAsStream), it fails finding the same files in the thread launched from ExecutorService.

The ClassLoader in action is the ContextFinder. When debugging the application, and suspending both threads, the "main thread" that originally started JBehave and the "story thread" launched from the executor service to run the story file, I can identify that the instance of the classloader is the same. Also the instances of the parents, etc.

But a call to

Thread.currentThread().getContextClassLoader().getResource("HelloWorld.story")

works perfectly in the main thread, and fails for the story thread and returns null.

Judging from the source code of the ContextFinder, it seems like it does little else then gathering all the ClassLoaders for the classes on the stack. So I tried this:

SomeClass.class.getClass().getClassLoader().getResource("HelloWorld.story")

... with the same result.

This is too strange for me. Any pointer for debugging or reasing why this behavior is showing is appreciated!


Solution

  • The thread context classloader (TCCL) is basically undefined in OSGi. You should avoid using it.

    As an extension to standard OSGi, Equinox does supply this thing called the ContextFinder, which performs stack inspection to try to find the topmost OSGi bundle classloader in the call stack. However you get almost no control, and the result can be quite unexpected as you have seen. Of course since this is an Equinox-specific extension, any code that relies on ContextFinder working properly will fail on all other OSGi Frameworks.

    So, rather than wasting time trying to debug this, it's better just not to use the TCCL at all. If you want to load a resource relative to a specific class, then do it from the literal class object, e.g.:

    MyClass.class.getResource("HelloWorld.story");
    

    UPDATE:

    I noticed this in your original question: SomeClass.class.getClass(). The result of this will be the class-object of java.lang.Class itself. Calling getClassLoader() on that will always return the JVM boot class loader.... probably not what you intended!