I have read a lot about Java classloaders, but so far I have failed to find an answer for this simple question:
I have two versions of com.abc.Hello.class in jars v1.jar and v2.jar. I want to use both in my application. What is the simplest way of doing this ?
I don't expect to be that simple, but something along these lines would be awesome :
Classloader myClassLoader = [magic that includes v1.jar and ignores v2.jar]
Hello hello = myclassLoader.load[com.abc.Hello]
And in a different class :
Classloader myClassLoader = [magic that includes v2.jar and ignores v1.jar]
Hello hello = myclassLoader.load[com.abc.Hello]
I would like to avoid using OSGi.
You're going the right way. You must take some things into account.
The normal thing is classes that exist in parent classloaders are used. So if you want two versions those classes must not be there.
But if you want to interact you can use reflection, or even better a common interface. So I'll do this:
common.jar:
BaseInterface
v1.jar:
SomeImplementation implements BaseInterface
v2.jar:
OtherImplementation implements BaseInterface
command-line:
java -classpath common.jar YourMainClass
// you don't put v1 nor v2 into the parent classloader classpath
Then in your program:
loader1 = new URLClassLoader(new URL[] {new File("v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
loader2 = new URLClassLoader(new URL[] {new File("v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());
Class<?> c1 = loader1.loadClass("com.abc.Hello");
Class<?> c2 = loader2.loadClass("com.abc.Hello");
BaseInterface i1 = (BaseInterface) c1.newInstance();
BaseInterface i2 = (BaseInterface) c2.newInstance();
You have almost written the solution.
I hope the following code fragment will help.
ClassLoader cl = new URLClassLoader(new URL[] {new File("v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
Class<?> clazz = cl.loadClass("Hello");
Replace v1.jar by v2.jar and this code will load version #2.
For a sample implementation of the accepted answer by #helios checkout github.com/atulsm/ElasticsearchClassLoader
it depends on what are u going to do with both versions and how, but in general you can at least load 2 version of class in different classloaders, and then set the Thread.contextClassloader() and play...
see http://www.javaworld.com/javaqa/2003-06/01-qa-0606-load.html and http://docs.oracle.com/javase/jndi/tutorial/beyond/misc/classloader.html
Related
This code can load the same class twice by two distinct Classloader.
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws Exception {
URL childAndParentClassesLocation = new URL("file:///C:\\Users\\Machi\\Desktop\\tobeloaded-1.0-SNAPSHOT.jar");
ClassLoader cl1 = new URLClassLoader(new URL[]{childAndParentClassesLocation}, Main.class.getClassLoader());
ClassLoader cl2 = new URLClassLoader(new URL[]{childAndParentClassesLocation}, Main.class.getClassLoader());
Class child1 = cl1.loadClass("example.ToBeLoaded");
Class child2 = cl2.loadClass("example.ToBeLoaded");
System.out.println(child2 == child1);
System.out.println(child2.equals(child1));
}
}
But a URLClassLoader has the Application ClassLoader as a parent. According to the parent delegation principle, should not the ToBeLoaded class be loaded by the Application ClassLoader once and only once?
If the parent delegation principle is in effect you are right. But that is not always the desired behaviour.
In JEE containers this is per design different. Just imagine two different applications refer to the same class (named mypackage.MyClass). But for some reason one application expects the library 'mypackage' in version 1, while the other in version two. And both of them are incompatible.
Thus JEE containers assign one classloader for each of the applications, and each of them will load that specific version of the class. They reside in memory in parallel, and they are distinct different types, so if you try to cast one into the other you will see an exception raised.
So to really know whether two classes are the same, not only compare their fully qualified class names but also compare the classloader they belong to. I am not aware that at runtime the JVM would check if two classes are the same (content-wise), as it might pop up in your example.
Basically our code allows people to pass in a custom implementation of our IRepository interface if they want to define their own repository for the files we generate. I keep getting the following error:
ClassCastException: S3RepositoryPlugin.S3Repository cannot be cast to package_name.IRepository
This is how I define the S3Repository:
public class S3Repository implements IRepository
And this is how im trying to insatiate it:
URL[] urls = { new URL("jar:file:" + assembly +"!/") };
URLClassLoader cl = URLClassLoader.newInstance(urls);
Class classToLoad = Class.forName(className, true, cl);
IRepository externalRepo = (IRepository) classToLoad.newInstance();
Where assembly is the full path to the jar, and className is "S3RepositoryPlugin.S3Repository".
Any idea as to why this is happening? From the stuff Im seeing online it seems casting to interfaces works differently than casting to classes but im not sure if the code im using is necessarily affected by that.
NOTE:
Using Tomcat9 and java8
I had to add the following code to the URLClassLoader to make it work:
URLClassLoader cl = URLClassLoader.newInstance(urls, getClass.getClassLoader());
My project structure is the following (very simplified of course):
So under lib-ext i download on a daily basis from a Jenkins server 2 jar files 'jar1 and jar2' to be checked by my program, i need one file from 'jar1' lets call it: "Class2Bloaded".
The issue is that this file implements an interface that is to be found in 'jar2', lets call this 'Dependency'
What i would like to do is, from my class under src "ClassThatLoads.java", load 'Class2Bloaded.class' and tell the class loader to look into 'jar2' to search for the implementing interface "Dependency.class"
My code so far (omitting exceptions handling):
//Create the URL pointing to Jar1
private URL getJarUrl(JarFile jarFile)
{
return new File(jarFile.getName()).toURI().toURL();
}
URL jar1Url = getJarUrl(jar1);
ClassLoader jar1classLoader = new URLClassLoader(new URL[] { jar1Url });
Class<?> Class2Bloaded = Class.forName(fullClassName, false, jar1classLoader );
So the problem happens within the Class.forName invocation, because the class i want to load implements an interface that is in jar 2.
Exception in thread "main" java.lang.NoClassDefFoundError: com/packagewithinJar2/Dependency
So eventually i have prepared another class loader that points to 'jar2', and i have even got the actual Interface i need:
URL jar2Url = getJarUrl(jar2);
ClassLoader jar2classLoader = new URLClassLoader(new URL[] { jar2Url });
Class<?> Interface2Bloaded = Class.forName(fullClassName, false, jar2classLoader );
Where 'fullClassName' in the second case is the fully qualified name of the interface from which 'Class2Bloaded' depends on.
Is just that i cant find anything in the javadocs of ClassLoader that allows me to 'inject' an additional class loader for the dependencies.
I hope my explanation is clear.
The first thing to do would be to add jar2 to the list of jars your URLClassLoader reads:
ClassLoader jarclassLoader = new URLClassLoader(new URL[] { jar1Url, jar2Url });
BUT the normal thing to do would be to add jar1 and jar2 on your classpath from the beginning.
To do so you would use the -cp parameter of the java executable.
for example, if you compile your classes into the bin directory:
java -cp libext/jar1.jar:libext/jar2.jar:bin ClassThatLoads
That way, you could use the classes seamless in your own java source and get rid of the cumbersome loading part :
public class ClassThatLoads {
public static void main(String[] args) {
Class2Bloaded stuff = new Class2Bloaded();
//use stuff from here...
}
}
I am loading classes via ClassLoader:
Class<?> clazz = urlClassLoader.loadClass(name.substring(0, name.length() - 6).replaceAll("/", "."));
System.out.println(clazz);
System.out.println(clazz.isInstance(SkillCast.class));
System.out.println(SkillCast.class.isInstance(clazz));
System.out.println(SkillCast.class.isAssignableFrom(clazz));
System.out.println(clazz.isAssignableFrom(SkillCast.class));
This is my structure:
public class SkillFireball extends SkillCast implements ISkillThrown
public abstract class SkillCast extends Skill
And prints are:
class skills.SkillFireball
false
false
false
false
What I am sure of is that clazz is SkillFireball and I can print field/method names.
How can I check if clazz is child of SkillCast?
EDIT
private static URLClassLoader urlClassLoader;
And code:
ClassLoader cl = Loader.instance().getModClassLoader();
urlClassLoader = URLClassLoader.newInstance(urls.toArray(new URL[urls.size()]), cl);
Where #getModClassLoader() returns:
// The class loader we load the mods into.
private ModClassLoader modClassLoader;
And:
public class ModClassLoader extends URLClassLoader
How it works:
Minecraft Forge API is loading #Mod. My mod is providing SkillCast.class and when loaded is attempting to read game directory and load classes in .jar files. Now - I have not much of an idea what should I do :C
I did it.
Problem was really my lack of knowledge about how classLoader-parenting works and how Java loads classes.
Done it by making new URLClassLoader with parent set to ClassLoader used by Main program itself (the mentioned #Mod).
SkillCast.class is loaded by Mod's ClassLoader therefore when I added new Class (SkillFireball) I also needed to use Mod's one, not new one like I did before.
Class<?> clazz = Loader.instance().getModClassLoader().loadClass(...)
This is not really an "answer" just an closing-problem post.
How do you change the CLASSPATH of a Java process from within the Java process?
Before you ask me "Why would you want to do that?" I'll explain it shortly.
When you have a Clojure REPL running it is common to need more jars in your CLASSPATH to load a Clojure source file, and I'd like to do it without having to restart Clojure itself (which is not really an option when using it on Slime on Emacs).
That's the reason but I don't want this question tagged as some-weird-language some-weird-editor and be disregarded by the majority of Java developers that may have the answer.
Update Q4 2017: as commented below by vda8888, in Java 9, the System java.lang.ClassLoader is no longer a java.net.URLClassLoader.
See "Java 9 Migration Guide: The Seven Most Common Challenges"
The class loading strategy that I just described is implemented in a new type and in Java 9 the application class loader is of that type.
That means it is not a URLClassLoader anymore, so the occasional (URLClassLoader) getClass().getClassLoader() or (URLClassLoader) ClassLoader.getSystemClassLoader() sequences will no longer execute.
java.lang.ModuleLayer would be an alternative approach used in order to influence the modulepath (instead of the classpath). See for instance "Java 9 modules - JPMS basics".
For Java 8 or below:
Some general comments:
you cannot (in a portable way that's guaranteed to work, see below) change the system classpath. Instead, you need to define a new ClassLoader.
ClassLoaders work in a hierarchical manner... so any class that makes a static reference to class X needs to be loaded in the same ClassLoader as X, or in a child ClassLoader. You can NOT use any custom ClassLoader to make code loaded by the system ClassLoader link properly, if it wouldn't have done so before. So you need to arrange for your main application code to be run in the custom ClassLoader in addition to the extra code that you locate.
(That being said, cracked-all mentions in the comments this example of extending the URLClassLoader)
And you might consider not writing your own ClassLoader, but just use URLClassLoader instead. Create a URLClassLoader with a url that are not in the parent classloaders url's.
URL[] url={new URL("file://foo")};
URLClassLoader loader = new URLClassLoader(url);
A more complete solution would be:
ClassLoader currentThreadClassLoader
= Thread.currentThread().getContextClassLoader();
// Add the conf dir to the classpath
// Chain the current thread classloader
URLClassLoader urlClassLoader
= new URLClassLoader(new URL[]{new File("mtFile").toURL()},
currentThreadClassLoader);
// Replace the thread classloader - assumes
// you have permissions to do so
Thread.currentThread().setContextClassLoader(urlClassLoader);
If you assume the JVMs system classloader is a URLClassLoader (which may not be true for all JVMs), you can use reflection as well to actually modify the system classpath... (but that's a hack;)):
public void addURL(URL url) throws Exception {
URLClassLoader classLoader
= (URLClassLoader) ClassLoader.getSystemClassLoader();
Class clazz= URLClassLoader.class;
// Use reflection
Method method= clazz.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true);
method.invoke(classLoader, new Object[] { url });
}
addURL(new File("conf").toURL());
// This should work now!
Thread.currentThread().getContextClassLoader().getResourceAsStream("context.xml");
I don't believe you can - the right thing to do (I believe) is create a new classloader with the new path. Alternatively, you could write your own classloader which allows you to change the classpath (for that loader) dynamically.
There's no need to write your own class loader! There's clojure.lang.DynamicClassLoader.
http://blog.japila.pl/2011/01/dynamically-redefining-classpath-in-clojure-repl/
You may want to look into using java.net.URLClassLoader. It allows you to programmatically load classes that weren't originally in your classpath, though I'm not sure if that's exactly what you need.
It is possible as seen from the two links below, the method VonC gives seems to be the best but check out some of these posts and google for "Java Dynamic Classpath" or "Java Dynamic Class Loading" and find out some info from there.
I'd post in more depth but VonC has pretty much done the job.
From Dynamic loading of class and Jar files.
Also check this sun forum post.
String s="java -classpath abcd/ "+pgmname+" "+filename;
Process pro2 = Runtime.getRuntime().exec(s);
BufferedReader in = new BufferedReader(new InputStreamReader(pro2.getInputStream()));
is an example of changin the classpath in java program