views:

661

answers:

4

Here is the problem I'm running into. There's a huge legacy application that runs on java 1.3 and uses external API, say, MyAPI v1.0. The exact implementation of MyAPI 1.0 is located somewhere in the classpath used by the app. There's also a mechanism that allows that app to use external code (some kind of plugin mechanism). Now I have another java library (MyLib.jar) that uses MyAPI v2.0 (which is NOT 100% backward compatible with v1.0) and I have to use it from the original app using that plugin mechanism. So I have to somehow let two (incompatible!) versions of the same API work together. Specifically, I want to use MyAPI v2.0 when API classes are invoked from MyLib.jar classes and use MyAPI 1.0 in all other cases.

MyAPI 1.0 is in the classpath, so it will be used by default, that's ok. I can create my own version of class loader to load classes from MyAPI 2.0 - no problem. But how do I fit it all together? Questions:

  1. MyLib.jar objects instantiate a lot(!) of instances of classes from MyAPI 2.0. Does this mean that I will have to do ALL these instantiations via reflection (specifying my own classloader)? That's a hell of a work!

  2. If some of MyAPI 2.0 objects gets instantiated and it internally instantiates another object from MyAPI, what classloader will it use? Would it use my class loader or default one?

  3. Just generally, does my approach sound reasonable? Is there a better way?

+2  A: 

You need to be careful with class loaders. If you do what you were suggesting, you would almost always end up MyAPI 1.0 even when using your class loader for MyAPI 2.0. The reason for this is how classes are loaded using the class loader. Classes are always loaded from the parent class loader first.

"The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance. " (http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html)

To provide isolation between the two APIs properly, you would need 2 class loaders (or 2 in addition to the main application one).

Parent - System classloader
  |- Classloader1 - Used to load MyAPI 1.0
  |- Classloader2 - Used to load MyAPI 2.0

Now to your questions. What you probably want to do is move most of the logic that uses the API into the classloaders. In addition to MyAPI 1.0/2.0, you should load the part of the application that uses those. Then the parent application just has to call a method that uses the API. This way you make a single reflection call to start the application and everything inside that application just uses standard references.

Chris Dail
The "classes are always loaded from the parent classloader first" rule can be broken by a custom classloader. With caution.
Darron
A: 

You can do this with a fancy ClassLoader without reflection.

Basically, the classloader has to say "if the loading class is from this jar, load from classpath B, otherwise use the main ClassLoader".

It's a little more complicated than that, but if you start from that idea you'll get it worked out.

Darron
A: 

This sounds reasonable. In the [API javadoc for loadClass][1] it says:

"Loads the class with the specified binary name. The default implementation of this method searches for classes in the following order: Invoke findLoadedClass(String) to check if the class has already been loaded.

Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.

Invoke the findClass(String) method to find the class."

If CL1 is for MyAPI1, CL2 is for MyAPI2, and CL3 is for MyLib it sounds like you want them to be checked in the order: CL3, CL2, CL1. Which from the above quote (as parents are checked first) suggests you want CL1 to have parent CL2, and CL2 to have parent CL3. As the constructor with a parent classloader is protected, you'll have to use URL classloader with the parents set appropriately.

Something like

URLCLassLoader cl3 = new URLClassLoader(new URL[]{ path to MyLib});
URLCLassLoader cl2 = new URLClassLoader(new URL[]{ path to API2}, cl3);
URLCLassLoader cl1 = new URLClassLoader(new URL[]{ path to API1}, cl2);

then use cl1 everywhere.

[1]: http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ClassLoader.html#loadClass(java.lang.String, boolean)

Nick Fortescue
+3  A: 

Let's start from answering your 2nd question: when referring from some class to another class it will be loaded by the same classloader that loaded the original class (unless of course the classloader didn't succeed in finding the class and then it will delegate to its parent classloader).

Having said that, why won't your entire MyLib.jar be loaded by a custom classloader, and then it can refer to the newer version of the API in a regular way. Otherwise you have a problem, because you will have to work with the Object type and reflection all the way through.

Stas