Chained classloader conundrum - java

I'm having some hard time with Java classloaders, maybe somebody could shed some light on this. I have extracted the essence of the problem to the follwing:
There are three classes - ClassLoaderTest, LoadedClass and LoadedClassDep. They are all on different paths.
ClassLoaderTest instantiates a new URLClassLoader - myClassLoader, priming it with the paths to the remaining two classes and it's own classloader (i.e. the application classloader) as parent. It then uses Class.forName("com.example.LoadedClass", true, myClassLoader) to load the LoadedClass through reflection. The LoadedClass imports the LoadedClassDep. If I run the above, using:
java -cp /path/to/the/ClassLoaderTest ClassLoaderTest "/path/to/LoadedClass" "/path/to/LoadedClassDep"
and using the command line arguments to prime the URLClassLoader everything works fine. Using static initialisers I confirm that the two classes are loaded with an instance of a URLClassLoader.
HOWEVER, and this is the problem, if I do:
java -cp /path/to/the/ClassLoaderTest:/path/to/the/LoadedClass ClassLoaderTest "/path/to/LoadedClassDep"
this fails to load the LoadedClassDep (ClassNotFoundException). The LoadedClass is loaded correctly, but with sun.misc.Launcher$AppClassLoader, not the URLClassLoader!
It would appear that since the application classloader is capable of loading the LoadedClass it also attempts to load the LoadedClassDep, disregarding the URLClassLoader.
Here's the full source code:
package example.bc;
public class ClassloaderTest {
public static void main(String[] args) {
new ClassloaderTest().run(args);
}
private void run(String[] args) {
URLClassLoader myClasLoader = initClassLoader(args);
try {
Class<?> cls = Class.forName("com.example.bc.LoadedClass", true, myClasLoader);
Object obj = cls.newInstance();
cls.getMethod("call").invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
private URLClassLoader initClassLoader(String[] args) {
URL[] urls = new URL[args.length];
try {
for (int i = 0; i < args.length; i++) {
urls[i] = new File(args[i]).toURI().toURL();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
return new URLClassLoader(urls, getClass().getClassLoader());
}
}
package com.example.bc;
import com.bc.LoadedClassDep;
public class LoadedClass {
static {
System.out.println("LoadedClass " + LoadedClass.class.getClassLoader().getClass());
}
public void call() {
new LoadedClassDep();
}
}
package com.bc;
public class LoadedClassDep {
static {
System.out.println("LoadedClassDep " + LoadedClassDep.class.getClassLoader().getClass());
}
}
I hope I made this clear enough. My issue is, I only know the path to ClassLoadeTest at compile time, I have to use strings at runtime for the other paths. So, any ideas how to make the second scenario work?

I'd expect the application classloader to load LoadedClass in the second case, since classloaders delegate to their parent initially - this is the standard behaviour. In the second case, LoadedClass is on the parent's classpath, so it loads the class instead of giving up and letting the URLClassLoader try.
The application classloader then attempts to load the LoadedClassDep because it is imported and referenced directly in LoadedClass:
public void call() {
new LoadedClassDep();
}
If you need to load these classes dynamically and independently at runtime, you can't have direct references between them in this way.
It is also possible to change the order in which classloaders are tried - see Java classloaders: why search the parent classloader first? for some discussion of this.

Related

Intercepting ClassLoader.getResource(String) calls, with a custom ClassLoader

We're trying to debug an unreproducible issue with WebStart, where access to resources inside Jars will "randomly" fail. Maybe one every 1000 application run will end with this error, which can happen anywhere where resources are read from a jar.
Searching in Google and the Java Bug database brought nothing similar (or at least, nothing helpful).
We are trying to get more info into what happens on the client by "instrumenting" the application so we track all calls to ClassLoader.getResource(String) (including indirectly over ClassLoader.getResourceAsStream(String)). Without changing the app code, I have created a "launcher" that would run the whole app with a custom classloader.
Unfortunately, it seems my ClassLoader is somehow bypassed. I do not see any of the expected System.out output. Here is what I tried:
private static final class MyClassLoader extends ClassLoader {
private MyClassLoader() {
super(TheClassThatMainIsIn.class.getClassLoader());
}
#Override
public URL getResource(String name) {
System.out.println("getResource("+name+")");
// Snip
return super.getResource(name);
}
#Override
public InputStream getResourceAsStream(String name) {
System.out.println("getResourceAsStream("+name+")");
final URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (final IOException e) {
return null;
}
}
}
public static void main(String[] args) {
System.out.println("Starting MyRealApp Launcher ...");
final MyClassLoader loader = new MyClassLoader();
try {
Class<?> realAppClasss = loader.loadClass("MyRealAppClass");
Method main = realAppClasss.getMethod("main", String[].class);
main.invoke(null, (Object) args);
} catch (final RuntimeException e) {
throw e;
} catch (final Error e) {
throw e;
} catch (final InvocationTargetException e) {
final Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new UndeclaredThrowableException(cause);
} catch (final Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
What am I doing wrong here?
Yes. This works, in principal.
However, you've to account how the resource loading code get's to the class loader. Since the class don't show up, it looks like they use the parents class loader.
You've to account different scenarios:
Code using context class loader, like:
Thread.currentThread().getContextClassLoader().getResource("via-context");
This is easy to achieve, by setting it before calling into main:
Thread.currentThread().setContextClassLoader(loader);
Method main = realAppClasss.getMethod("main", String[].class);
main.invoke(null, (Object) args);
Next thing you've to account is code which 'takes' class loader from current class, and load it that. When you're class is loaded via the parent class loader, it will also use that class loader to get the resource. Like:
MyRealAppClass.class.getResource("via-class");
MyRealAppClass.class.getClassLoader().getResource("via-class");
objectInfApp.getClass().getClassLoader().getResource("via-class");
To avoid that you've to ensure that the apps classes are actually loaded with your class loader, not the parent. For a simple main, you can extend from the URL class loader, skip any parent and user the original class path for the URL's. Like:
// URL class loader to lookup in jars etc
private static class MyClassLoader extends URLClassLoader
{
public MyClassLoader(URL[] urls) {
// Use the given URLs and skip any parent class loader, directly go to the system loader
super(urls,null);
}
// ...
// Then setup the class path
String[] classPath = System.getProperty("java.class.path").split(";");
URL[] classPathUrls = new URL[classPath.length];
for (int i = 0; i < classPath.length; i++) {
classPathUrls[i] = new File(classPath[i]).toURL();
}
MyClassLoader loader = new MyClassLoader(classPathUrls);
This should cover the most basic cases. When you're actual application itself has more class loader trickery, there might more you need to setup.

Java dynamic loading a class issue

I am trying to write a code that compiles and runs another java class, after it creates it from a String.
My problem is when I run
Class classToLoad = null;
ClassLoader classLoader = Server.class.getClassLoader();
try {
classToLoad = classLoader.loadClass(className);
} catch (Exception e) {
e.printStackTrace();
}
It throws a ClassNotFoundException. My problem isn't about the package, because if I debug the code and place a breakpoint before the "getClassLoader" and I reload the classes, then my code works fine and it sees the class that was recently created earlier in the app.
How can I reload the classes during runtime so the loadClass will work?
Take a look at this tutorial:
ClassLoader Load / Reload Example
... Let's look at a simple
example. Below is an example of a simple ClassLoader subclass. Notice
how it delegates class loading to its parent except for the one class
it is intended to be able to reload. If the loading of this class is
delegated to the parent class loader, it cannot be reloaded later.
Remember, a class can only be loaded once by the same ClassLoader
instance.
As said earlier, this is just an example that serves to show you the
basics of a ClassLoader's behaviour. It is not a production ready
template for your own class loaders. Your own class loaders should
probably not be limited to a single class, but a collection of classes
that you know you will need to reload. In addition, you should
probably not hardcode the class paths either.
public class MyClassLoader extends ClassLoader{
public MyClassLoader(ClassLoader parent) {
super(parent);
}
public Class loadClass(String name) throws ClassNotFoundException {
if(!"reflection.MyObject".equals(name))
return super.loadClass(name);
try {
String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
"classes/reflection/MyObject.class";
URL myUrl = new URL(url);
URLConnection connection = myUrl.openConnection();
InputStream input = connection.getInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int data = input.read();
while(data != -1){
buffer.write(data);
data = input.read();
}
input.close();
byte[] classData = buffer.toByteArray();
return defineClass("reflection.MyObject",
classData, 0, classData.length);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Below is an example use of the MyClassLoader.
public static void main(String[] args) throws
ClassNotFoundException,
IllegalAccessException,
InstantiationException {
ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
Class myObjectClass = classLoader.loadClass("reflection.MyObject");
AnInterface2 object1 =
(AnInterface2) myObjectClass.newInstance();
MyObjectSuperClass object2 =
(MyObjectSuperClass) myObjectClass.newInstance();
//create new class loader so classes can be reloaded.
classLoader = new MyClassLoader(parentClassLoader);
myObjectClass = classLoader.loadClass("reflection.MyObject");
object1 = (AnInterface2) myObjectClass.newInstance();
object2 = (MyObjectSuperClass) myObjectClass.newInstance();
}
Probably asking: "What is the context in which you are loading the class?" will help answer your question better.
Most standard frameworks like Spring handle loading classes internally and exposing only the methods that those classes provide.
Try Class.forName(String name) to attempt to load the class and return the handle to the class object.
If you want to specifically use your own classloader to load the class, use the overloaded: Class.forName(String name, boolean initialize, ClassLoader loader)
But you will need to ensure that your classloader is able to locate the class to load correctly.
For the classloader you are using, try:
Thread.currentThread().getContextClassLoader()

URLClassLoader throwing NullPointerException when loading class

Part of the (Android) app I'm working on requires a 'plugins' folder that users can drop their own code into, as a jar file. The app will scan the folder for jar files, and try to load all the classes it finds. Right now, the app is not loading any classes at all, because it seems all calls to URLClassLoader.loadClass() result in a null pointer.
private static URLClassLoader classLoader;
private static List<Class<? extends OpMode>> opModeList;
private static void attemptLoadClass(String entryname) {
String classname = entryname.replace('/', '.');
classname = classname.substring(0, classname.length() - 6);
try {
Class<?> c = classLoader.loadClass(classname);
// ^^^ Problems are happening right here ^^^
Object instance = c.newInstance();
if (instance instanceof OpMode) {
opModeList.add(((OpMode)instance).getClass());
}
} catch (Throwable ex) {
ex.printStackTrace();
}
}
classLoader is constructed with an array of URL's leading to all the jar files that will need to be loaded. I should also mention that the code only seems to throw inexplicable errors when it's running on an Android device, and not on my computer.
You seem to be saying that the NPE is thrown by this line of code:
Class<?> c = classLoader.loadClass(classname);
If so, there is only one possible explanation: classloader is null.

JarClassLoader - loading jars dynamically - how to?

I have seen many class loader questions, but still was not able to figure why, the error here.
I am writing a program which uses 2 versions of jars. One is needed to get content from older storage, and another to store content in new storage.
Since, I need either of the jar to be loaded at a time, I used JarClassLoader to create a proxy for adding one jar and loading its classes. But I face ClassNotFoundException.
public class HbaseMigrator implements Runnable {
public void run() {
JarClassLoader jcl = new JarClassLoader();
jcl.add("hadoop-0.13.0-core-modified-1.jar");
Object obj1 = JclObjectFactory.getInstance().create(jcl, "UserMigThreadImpl", toProcessQueue,threadName, latch,DBUtil,lock);
MigThread mig = JclUtils.cast(obj1, MigThread.class, jcl);
Thread.currentThread().setContextClassLoader(jcl);
try {
Method method = MigThread.class.getMethod("callthis", new Class[]{});
method.invoke(mig, new Object[]{});
// mig.callthis();
} catch( Exception e) {
e.printStackTrace();
} catch(Error er) {
er.printStackTrace();
}
}
}
Method called is:
public void callthis() {
DFSUtil = new DFSAccessAPIImpl();
.........
}
This class instantiation internally uses hadoop modified jar, which is not picked up from my classloader and it throws ClassNotFoundException.
What is that I am doing wrong ?
JarClassLoader used here is jcloader :
org.xeustechnologies.jcl.JarClassLoader
I experienced this problem with loading plugins to my application, so I decided to try to load all .class files from all jars in path. Maybe this code snipped from my app will help you.
https://bitbucket.org/rsohlich/plagdetector/src/432b52f252ff7647221b7e91b08731bd9cbe2a70/PlagDetectorSpring/src/main/java/cz/sohlich/app/service/impl/PluginHolderImpl.java

Java Annotations don't work with external Jar Files

So, I'm currently working on a plugin system (plugins are jar files located in a plugins folder) for my command line "operating system". Works fine but the API doesn't.
I am using Annotations to get the Plugin main class and the event classes. However, it doesn't seem to find my Annotations.
This is what I use to load my plugins:
public static void loadPlugin(File file)
{
try
{
URL urlList[] =
{ new File("plugins/" + file.getName()).toURI().toURL() };
URLClassLoader classLoader = new URLClassLoader(urlList);
Class<?> pluginclass, eventclass = pluginclass = null;
String name, version = name = null;
Priority priority = Priority.NORMAL;
String[] classes = Utils.getJarClasses(file, "plugin").toArray(new String[]
{});
for (String s : classes)
{
System.out.println(s);
Class<?> c = classLoader.loadClass(s);
Plugin p = c.getAnnotation(Plugin.class);
EventHandler e = c.getAnnotation(EventHandler.class);
if (p != null)
{
System.out.println("not null!");
pluginclass = c;
name = p.name();
version = p.version();
priority = p.priority();
}
if (e != null)
{
eventclass = c;
}
}
switch (priority)
{
case NORMAL:
plugins.add(new Class<?>[]
{ pluginclass, eventclass });
case DEVELOPER_API:
apis.add(new Class<?>[]
{ pluginclass, eventclass });
}
print("Loaded Plugin: " + name + " V" + version + " with priority " + priority.toString() + ".");
} catch (Exception e)
{
e.printStackTrace();
}
}
It's printing out the class it's currently looping through just fine, but p and e are always null, no matter what. version and name are always null, too, obviously.
My Annotation classes have got #Retention(RetentionPolicy.RUNTIME), that's the main reason why I'm asking here.
I have tested a similar system, just that one didn't use jar files (classes inside of a package, instead) and worked fine.
Thanks in advance.
One reason that could explain the described behavior is that you are mixing class loaders: an annotation will only be visible if both the annotation class and the annotated type are loaded by the same class loader. This has to do with the use of the Reflection API as the mechanism employed to actually perform the annotation lookup process.
In your code you are using a custom class loader, classLoader, to load the classes from an external jar file, but both the Plugin and EventHandler classes are loaded by the class loader running your loadPlugin method.
In order to solve the problem, I think you can provide the class loader which is running the loadPlugin method as a parent class loader of the custom one, something like:
URLClassLoader classLoader = new URLClassLoader(urlList, YourClass.class.getClassLoader());
A similar behavior has been also formerly reported here, in stackoverflow. See, for instance, the accepted answers of this question and this other one.

Categories

Resources