0

When I write code in nodejs, I can have multiple versions of the same library because 'require'-ing a library is not global.

The classpath is different. Each library is looking at its node_modules library.

However, in java, I cannot seem to be able to have multiple versions of same library in the classpath.

As the classpath is global.

Is there any way to make java behave more like node in that sense - by making each classloader have a different classpath?

why is my question different than that one

That question is based on "given a classpath, ... question .." while I am not assuming anything on the classpath. quite the contrary, I would like to have a different classpath for each library if possible.

Community
  • 1
  • 1
guy mograbi
  • 27,391
  • 16
  • 83
  • 122
  • There are several workarounds, but if you really need this, OSGI is your friend. – biziclop Jul 20 '16 at 09:37
  • http://stackoverflow.com/questions/9757649/which-java-class-file-will-be-called-if-same-class-is-packed-in-two-jar-files – sidgate Jul 20 '16 at 09:37
  • 3
    Possible duplicate of [Java, Classpath, Classloading => Multiple Versions of the same jar/project](http://stackoverflow.com/questions/6105124/java-classpath-classloading-multiple-versions-of-the-same-jar-project) –  Jul 20 '16 at 09:38
  • 1
    The main handle to support that are multiple classloaders and there are several libraries/servers that support that. Handling that manually can be quite cumbersome and error-prone. As an alternative you could deploy multiple versions of your applications (if that's what makes use of the different libraries) and let your external systems use those. Or if it is a single application you could try and split it with remote/webservice calls in between. – Thomas Jul 20 '16 at 09:39
  • @Thomas - but perhaps using `Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)` instead of `import` can resolve the matter? Perhaps in Java, using loadClass(String) is the parallel for `require(string)` in nodejs. with only the need to use a new classloader each time? but that will still not resolve my problem, will it. – guy mograbi Jul 20 '16 at 10:14
  • I'm not sure what your actual problem is except that you want to use multiple versions of the same library, the how would be important as well. Of course you can use different classloaders but that's quite some complex thing to do right and using reflection all the time is bound to harm performance thus I suggested at least considering different approaches. – Thomas Jul 20 '16 at 10:48
  • @biziclop - after reading a couple of days about osgi. i don't think it is my friend. it seems to be yet another container in java. I can't wrap my head around how is it different from running multiple processes. I don't want to have special support for something. I just want it to work. if there's a micro lib that exposes some kind of `require` method that creates a new classloader and returns an API class - that sounds more like it. but osgi sounds like an overkill for my requirements. I don't need a bundle manager/container. – guy mograbi Jul 22 '16 at 15:31
  • @guymograbi Fair enough, if it's just for a handful of specific libraries, it's fairly easy to do it by hand. – biziclop Jul 22 '16 at 20:58
  • @biziclop - how about ServiceLoader? can I use it somehow? never heard about it. – guy mograbi Jul 23 '16 at 10:33

1 Answers1

0

Ok, I read a lot about it.. I have a long answer and a short one.

The long one - I will write a blog post about it and link it here..

Here is the short one

Assumptions

  • dep_1.jar - is a dependency I have, and is not dependent on any other jar. (a single jar packaging)
  • The API is available in my classpath. I only want to separate the implementation's classpath.
  • The class I want to instantiate has an empty constructor

Lets talk about a specific use case

// jar dep_1-api.jar
package guymograbi; 
public interface IMyMessage {
     public String getMessage();

}

// jar dep_1.jar
package guymograbi;
public class MyMessage implements IMyMessage{
     public String getMessage(){ return "my message"; }
}

Code

ClassLoader classloader = new URLClassLoader(new URL[]{ new File("java_modules/dep_1.jar").toURI().toURL() })

return (IMyMessage.class) classloader.loadClass("guymograbi.MyMessage").newInstance();

This will return an IMyMessage implementation without changing my classpath.

I can then load another implementation of the same interface from another jar, etc.. etc..

This solution is:

  • small enough to embed in any library that wants to support it.
  • small enough to quickly wrap any existing library with it.
  • almost zero learning curve.. just a need to apply to an easy standard.

Further reading

So it seems there are many ways in which you can write this solution.

You can use Spring, Guice and ServiceLoader to do the actual implementation.

ServiceLoader - I kinda liked this solution has it has something similar to main entry in package.json. AND - obviously - it does not require a dependency that will mess with my classpath!

I also recommend checking io.github.lukehutch:fast-classpath-scanner that allows you to easily find all classes that implement a specific interface.

Next Step

The next step for me is to construct a proper java_modules folder and allow dep_1 to be a folder containing index.jar and its own java_modules folder and cascade the solution downwards..

I plan to do so using the answer from: How to get access to Maven's dependency hierarchy within a plugin

And then write a maven plugin (like assembly) to pack it all up properly.

Community
  • 1
  • 1
guy mograbi
  • 27,391
  • 16
  • 83
  • 122
  • Just a tiny remark, you should add a parent classloader to your custom URL classloader though as otherwise if your loaded JAR requires some dependencies loaded from, f.e. `java.lang` if wont be able to find them. On providing the paraent classloader they will as the delegation model of classloaders kicks in. – Roman Vottner Jul 23 '16 at 12:31
  • @RomanVottner Thanks for pointing that out. would `URLClassLoader urlClassLoader = new URLClassLoader(urls,ClassLoader.getSystemClassLoader());` do? – guy mograbi Jul 23 '16 at 17:09
  • depends on your setup. If you don't have a complex setup in place you can use `getClass().getClassLoader()` or `Thread.currentThread().getClassLoader()`. Have a look [at this question](http://stackoverflow.com/questions/11395074/what-loads-the-java-system-classloader) to learn the differences between the different classloaders. – Roman Vottner Jul 23 '16 at 19:05