Jon
Jon

Reputation: 5357

Integrating a third-party API into a Java application with clashing dependencies

I am working on an existing application which has quite a lot of external JAR dependencies. I need to enhance it to integrate with a third-party application which has an API. Sadly, the API is not well contained and also comes with a large number of its own dependencies some of which clash with mine.

I believe I should solve this using Classloaders, but I'm struggling to see how to structure them correctly.

To keep it simple, assume we have myapp.jar with a hibernate3.jar dependency, and vendor-api.jar with a hibernate2.jar dependency (and assume these are incompatible).

My new piece of code will reside in the myapp.jar library (although it could be in a separate jar if this would help). Due to the way the vendor API works, my new code needs to extend a class from the vendor-api.jar library.

How can I structure the Classloaders in such a way that anything within the vendor-api.jar accesses only its own dependencies, and anything on my side accesses only the myapp.jar and dependencies?

Thanks, Jon

Upvotes: 3

Views: 4778

Answers (3)

Jayan
Jayan

Reputation: 18459

Using OSGi may help you in the long term. Here is an implementation I am trying now- http://felix.apache.org

Upvotes: 0

Mike Tunnicliffe
Mike Tunnicliffe

Reputation: 10772

I've not tried this myself, but from memory each clashing class needs to be in a sibling classloader and any communication between the two needs to go through a common ancestor. However, the ancestor cannot (AFAIK) "directly" reference classes from its children and must access them through the reflection API.

Something along these lines ought to work (dry-coded) YMMV. Comments and error-spotting welcome.

class Orchestrator {
    URL[] otherAppClasspath = new URL[] { new URL("file:///vendor-api.jar"),
                                          new URL("file:///hibernate2.jar"),
                                          new URL("file:///code-extending-vendor-api.jar" };
    URLClassLoader otherAppLoader = new URLClassLoader(otherAppClasspath);

    URL[] yourAppClasspath = new URL[] { new URL("file:///myapp.jar"),
                                         new URL("file:///hibernate3.jar") };
    URLClassLoader yourAppLoader = new URLClassLoader(yourAppClasspath);

    public void start() {
        Method yourAppEntryPoint = yourAppLoader.findClass("com/company/Main").getMethod("start", new Class[] { Orchestrator.class } );
        yourAppEntryPoint.invoke(null, new Object[] { this });
    }

    public static void main(String[] args) {
        new Orchestrator().start();
    }

    // define some abstracted API here that can be called from your app
    // and calls down into classes in the other app
    public String getSomeResultFromOtherApp(int someArgument) {
        Method otherAppAPI = otherAppLoader.findClass("com/company/ExtendingAPIClass").getMethod("getSomeResult", new Class[] { Integer.class });
        return (String)otherAppAPI.invoke(null, new Object[] { someArgument });          
    }

}

Upvotes: 1

Stephen C
Stephen C

Reputation: 718926

@fd's answer gives a technical mechanism that ought to work - give or take some typos, exception handling, etc.

However, I think you would be better off not trying to do this ... unless your dependencies on the 3rd party APIs are restricted to a very small number of methods on a very small number of classes. Each class that you depend on has to be dynamically loaded and each method has to be looked up and invoked reflectively. Too much of that and your codebase will suffer.

If I were you, I'd try to resolve the dependency issue some other way:

  1. Try to get the 3rd party vendor to use hibernate3.jar
  2. Change your application to use hibernate2.jar
  3. Refactor so that your application code and the 3rd party library are in separate JVMs or separate webapps.

From what you say, this might be hard.

Upvotes: 0

Related Questions