views:

387

answers:

1

I'm a newbie to Java and just starting to figure out the concept of class loaders. Right now I am having some issues with log4j regarding its use of the thread context classloader.

I'm getting the following errors: A "org.apache.log4j.ConsoleAppender" object is not assignable to a "org.apache.log4j.Appender" variable. The class "org.apache.log4j.Appender" was loaded by [java.net.URLClassLoader@105691e] whereas object of type "org.apache.log4j.ConsoleAppender" was loaded by [sun.misc.Launcher$AppClassLoader@16930e2]. Could not instantiate appender named "CONSOLE".

My application works roughly this way: On init URLClassLoader #1 is constructed and loads some classes, these classes use log4j. Later on URLClassLoader #2 is constructed (which has URLClassLoader #1 as it's parent) and loads some more classes, these classes also use log4j. When URLClassLoader #2 is used to load these classes the above error message appears (there are a couple more with the same issue).

The current workaround I did was to set the current thread context classloader to URLClassLoader #2 before loading the problematic classes, and resetting it to the old one afterwards:

ClassLoader urlClassLoader; // this is URLClassLoader #2
Thread thread = Thread.currentThread();
ClassLoader loader = thread.getContextClassLoader();
thread.setContextClassLoader(urlClassLoader);
try {
  urlClassLoader.loadClass(...)
} finally {
  thread.setContextClassLoader(loader);
}

While this works, I am not sure if it's the right approach.

Any insight on this matter will be appreciated. Also, why is log4j forcing me to mess with the thread context classloader? Why not let me pass in a class loader (and use a default one when I don't) instead of using the thread's one?

+6  A: 

You appear to have stumbled upon the major problem with log4j (and the Apache Commons Logging library), namely that they have a ridiculously hard time discovering and interacting with the right classloaders as they're being used. There's a very dense explanation, complete with examples, here; the take-home message is that one of the primary driving forces for the new logging framework SLF4J was to eliminate these issues entirely. You might want to swap it in and see if your life is made any easier.

delfuego
I'm not sure if it's feasible for us to start using something else at this point. What's the suggested way of dealing with this log4j issue?
Idan K
Honestly, I can't say, because it's a bad enough issue that I now assiduously avoid log4j and Apache Commons Logging. Realistically, though, you should be able to migrate to SLF4J trivially -- see http://www.slf4j.org/legacy.html for information on how SLF4J comes with "bridge adapters" that allow your code to still make calls to log4j and have those calls intercepted by SLF4J. This way, you can use SLF4J natively in any new code, and migrate the old, log4j-utilizing code over to SLF4J at your leisure.
delfuego
+1 for the hint to avoid Log4j and JCL. They're replaced by SLF4j, which also has compatibility layers for log4j and jcl. We've done that in ALL our projects for exactly those reasons.
mhaller
do you have any articles explaining why to avoid log4j? I really can't come up with a suggestion like this without some background. Also some info about SLF4j being the new 'standard' can help.
Idan K
Daniel, the linked article (the "dense explanation" link) explains the problem, and the issue you're encountering should be the best example of all.
delfuego
thank you, I'll look into it and other questions regarding SLF4j on SO
Idan K