views:

407

answers:

4

I have a java project I want to create which will be built on top of some vendor APIs. The APIs connect to servers running said vendor's software and perform various actions.

I have 2 different versions of the servers supported by 2 different API versions. Any changes to the API's are in internal implementation only. I.E. The classes, interfaces, methods, etc. available to me in the older version exist in the newer version. Therefore the code I write should compile and run with either API version. There is a version number in the API presented to the servers when using the API to connect that prevents you from using a different version API on that particular server.

  1. Is there a way to switch JAR files on the fly at runtime? (something like a c/c++ DLL??)

  2. If switching API versions at runtime isn't possible, what is the most elegant way to handle the problem. Build the code 2x (one for each api version)?

I hope I'm missing something but approach 2 doesn't seem ideal. Here's a more concrete example of why:

package org.myhypotheticalwrapper.analyzer;

import org.myhypothetical.worker;
import org.myhypothetical.comparator;

public class Analyzer {

    Worker w1 = new Worker();
    Worker w2 = new Worker();

    Comparator c = new Comparator(w1.connectAndDoStuff(),w2.connectAndDoStuff());
    c.generateReport();
}

This is my dilema. I want w1 to be built with the old API and w2 be built with the new API so they can connect to the appropriate servers. Other than the API's they sit on top of, they are the same (identical code). Do I have to create two uniquely named Class types for W1 and W2 even though their code is identical, simply to accommodate different API versions? It seems like that could get unwieldy fast, if I had many API versions that I wanted to interact with.

Any suggestions and comments greatly appreciated.

-new guy

+3  A: 

The easiest is probably having a classloader loading in classes not in the default classpath.

From http://www.exampledepot.com/egs/java.lang/LoadClass.html

    // Convert File to a URL
    URL url = file.toURL();          // file:/c:/myclasses/
    URL[] urls = new URL[]{url};

    // Create a new class loader with the directory
    ClassLoader cl = new URLClassLoader(urls);

    // Load in the class; MyClass.class should be located in
    // the directory file:/c:/myclasses/com/mycompany
    Class cls = cl.loadClass("com.mycompany.MyClass");
Thorbjørn Ravn Andersen
A: 

You need to make sure that the classes are in different packages. You can't import two jar files with the same package listing and expect one to be recognized over the other. If they are in different packages you can use:

com.foo.Worker w1;
com.bar.Worker w2;
amischiefr
This is only true if you have two jar files referenced by the same classloader. If you use the same classloader then one set of classes will be used and the other won't. It is possible, however, to load two classes with the same package and same name in the same JVM. They just have to be loaded with different classloaders.
Peter Dolberg
Interesting, have a link to a good read on that? Google has failed me :(
amischiefr
+2  A: 

You can't really change out a class file once it's been loaded, so there's really no way to replace a class at runtime. Note that projects like JavaRebel get around this with some clever use of instrumentation via the javaagent - but even what you can do with that is limited.

From the sounds of it you just need to have two parallel implementations in your environment at the same time, and don't need to reload classes at runtime. This can be accomplished pretty easily. Assume your runtime consists of the following files:

  • analyzer.jar - this contains the analyzer / test code from above
  • api.jar - this is the common forward-facing api code, e.g. interfaces
  • api-impl-v1.jar - this is the older version of the implementation
  • api-impl-v2.jar - this is the newer version of the implementation

Assume your worker interface code looks like this:

package com.example.api;

public interface Worker {
  public Object connectAndDoStuff();
}

And that your implementations (both in v1 and v2) look like this:

package com.example.impl;

import com.example.api.Worker;

public class WorkerImpl implements Worker {
  public Object connectAndDoStuff() {
    // do stuff - this can be different in v1 and v2
  }
}

Then you can write the analyzer like this:

package com.example.analyzer;

import com.example.api.Worker;

public class Analyzer {
  // should narrow down exceptions as needed
  public void analyze() throws Exception {  
    // change these paths as need be
    File apiImplV1Jar = new File("api-impl-v1.jar"); 
    File apiImplV2Jar = new File("api-impl-v2.jar");

    ClassLoader apiImplV1Loader = 
      new URLClassLoader(new URL[] { apiImplV1Jar.toURL() });
    ClassLoader apiImplV2Loader = 
      new URLClassLoader(new URL[] { apiImplV2Jar.toURL() });

    Worker workerV1 = 
      (Worker) apiImplV1Loader.loadClass("com.example.impl.WorkerImpl")
                              .newInstance();
    Worker workerV2 = 
      (Worker) apiImplV2Loader.loadClass("com.example.impl.WorkerImpl").
                              .newInstance();

    Comparator c = new Comparator(workerV1.connectAndDoStuff(),
                                  workerV2.connectAndDoStuff());
    c.generateReport();
  }
}

To run the analyzer you would then include analyzer.jar and api.jar in the classpath, but leave out the api-impl-v1.jar and api-impl-v2.jar.

toluju
A: 

Your worker needs to have a delegate that implements an interface. You will have to write two delegates, one for the old api, one for the new. Choose which delegate to instantiate at runtime. Something like:

public class Worker {
private WorkerDelegate delegate;

public void foo() { delegate.foo(); }

public Object bar(){ return delegate.bar(); }

public Enum API{v1,v2};

public Worker(API api) {
try { switch(api){
case v1: delegate = Class.forName("my.v1.impl").newInstance(); break
case v2: delegate = Class.forName("my.v2.impl").newInstance(); break
}
}catch(...){throw new Error(e);}
}

}

More implementations can be added later with ease.

KitsuneYMG