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...
}
}
Related
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());
I am trying to use a custom class loader to load all the dependencies needed for the application. I've implemented the customerClassLoader following the site: https://www.javacodegeeks.com/2013/03/java-handmade-classloader-isolation.html
However, I dont understand how to tell my application to use the custom classLoader whenever needed.
For instance: Lets say, I have a method to make http request like below. How can I tell the application to use the custom classLoader to load the required jars?
private HttpResponse get() {
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request);
return response;
}
Java uses ClassLoader implicitly when you use new, import keyword, the jvm will use the current class's classloader to load the dependent classes, so you can use the custom classloader to load a bootstrap class explicitly by using classloader.loadclass, and the bootstrap just runs a method belonging to your target class instance. An example follows.
There is a class Target that depends on the class DateFormatter which is included in the spring-context, and has a method named start.
import org.springframework.format.datetime.DateFormatter;
public class Target {
private static DateFormatter dateFormatter;
public void start(){
System.out.println(this.getClass().getClassLoader());
dateFormatter=new DateFormatter();
System.out.println(dateFormatter);
}
}
Next, we compile and package the above code as a jar named target.jar, which is stored at D:\\test\\target.jar.
Next, we declare a class BootStrap in another jar that will call the method start of Target instance. The BootStrap class will dynamically load the target.jar and spring-context jar files by the same classloader which is a URLClassLoader instance. Because of this, the method start in Target instance can access the DateFormatter class that is defined in spring-context.
public class BootStrap {
public static void main(String[] args) throws Exception{
URL url = new URL("http://maven.aliyun.com/nexus/content/groups/public/org/springframework/spring-context/4.3.1.RELEASE/spring-context-4.3.1.RELEASE.jar?spm=0.0.0.0.kG1Pdw&file=spring-context-4.3.1.RELEASE.jar");
URL url2= (new File("D:\\test\\target.jar").toURI().toURL());
URLClassLoader classLoader = new URLClassLoader(new URL[]{url,url2});
Class<?> clz = classLoader.loadClass("com.zhuyiren.Target");
Object main = clz.newInstance();
Method test = clz.getMethod("start");
test.invoke(main);
}
}
Finally, run the BootStrap main method. There are two important thing:
The BootStrap class and Target class don't belong to a same jar file.
The target.jar is not stored in CLASSPATH path.
These 2 point can make sure that the AppClassLoader can not find and load the Target class. Because of the mechanism of class loader, jvm will use the custom load the Target. Of course, you can guarantee it by changing the URLClassLoader classLoader = new URLClassLoader(new URL[]{url,url2}); to URLClassLoader classLoader = new URLClassLoader(new URL[]{url, url2}, ClassLoader.getSystemClassLoader().getParent());
And we can see the result:
java.net.URLClassLoader#e9e54c2
org.springframework.format.datetime.DateFormatter#4dd8dc3
That means we can access the DateFormatter instance which is defined in spring-context jar file successfully, while the spring-context is not stored in CLASSPATH, but we are using the custom clasloader to load and use it.
This is a classloader issue that I am struggling with. I understand the root cause of the issue (different classloaders), but I'm not sure about the best way to fix it.
I have project with some common interfaces; let's call it api. I have two other projects called runner and module that both use api as a dependency.
The job of runner is to dynamically load a module artifact (from a jar; it's a fat one that includes its dependencies) and then execute it. runner expects module to provide certain concrete implementations from api. To make sure that classes from different versions of module.jar don't clobber each other, I create a new classloader with a URL to module.jar, and set the parent classloader to the classloader of the class that loads and processes module.jar. This works without any issues.
The problem arose when I used runner as a dependency inside a webapp (a spring boot app to be specific), and quickly found that I couldn't load some classes from module.jar because they conflict with classes that already exist in the current classpath (from other dependencies in the webapp).
Since module.jar really only needs the classes from api, I thought that I could create a new URLClassLoader (without a parent) that only has classes from api.jar, and then use that as the parent classloader when I load up the module. This is where I started running into trouble:
CommonInterface commonInterface = null;
Class<CommonInterface> commonInterfaceClass = null;
ClassLoader myClassLoader = URLClassLoader.newInstance(moduleJarURL, apiClassesClassLoader);
//...
//...
//clazz is a concrete implementation from module.jar
if(myClassLoader.loadClass(CommonInterface.class.getName()).isAssignableFrom(clazz)) {
commonInterfaceClass = clazz;
}
commonInterface = commonInterfaceClass.newInstance(); //ClassCastException
I understand that my original problem is due to the fact that the classloader first checks to see if the class has already been loaded before attempting to load it, which meant that when it was resolved using the name from module.jar, it was linking against an incompatible version of the class.
What's a good way to deal with this issue? Instead of creating a URL classloader that only has classes from api, does it make sense to create my own implementation that delegates to the parent only if the requested class is one from api?
You have loaded CommonInterface from two different class loaders. Classes with the same name but different class loaders are different classes to the JVM. (Even if the classes are 100% identical in the .class file - the problem is not incompatibility but the fact that they're from different class loaders)
If you do a
System.out.println(CommonInterface.class == myClassLoader.loadClass(CommonInterface.class.getName()));
You'll find that this prints false.
The way your create your classloader:
ClassLoader myClassLoader = URLClassLoader.newInstance(moduleJarURL, apiClassesClassLoader);
.. would only work if apiClassesClassLoader is also a parent class loader of the class that contains this code.
You could try:
ClassLoader myClassLoader = URLClassLoader.newInstance(moduleJarURL,
getClass().getClassLoader());
But from your description (it's a "fat" jar that contains its own dependencies) and the intricacies of the web classloader (child first) this may not solve your problem.
In that case, the only solution is to make your module jar "lean" to ensure that you only load each class once with one class loader only.
I forgot to update this question with my solution. I was able to solve this issue by creating a custom class-loader that extends URLClassLoader. This classloader does not have a parent.
I then overrode loadClass to control how classes were being loaded. I first check to see if the class exists in module.jar. If so, I load it from there. Otherwise, I load it using the current classloader. Since my custom classloader doesn't have a parent, it can load classes from module.jar even if they were already loaded by the main classloader, because they do not exist in my custom classloader's hierarchy.
The basic approach was like this:
public class MyClassLoader extends URLClassLoader {
private final ClassLoader mainClassLoader = MyClassLoader.class.getClassLoader();
private final Set<String> moduleClasses;
private MyClassLoader(URL url) {
super(new URL[]{ url });
try {
JarURLConnection connection = (JarURLConnection) url.openConnection();
this.moduleClasses = connection.getJarFile().stream()
.map(JarEntry::getName)
.filter(name -> name.endsWith(".class"))
.map(name -> name.replace(".class", "").replaceAll("/", "."))
.collect(Collectors.toSet());
} catch(IOException e) {
throw new IllegalArgumentException(String.format("Unexpected error while reading module jar: %s", e.getMessage()));
}
}
public static MyClassLoader newInstance(JarFile libraryJar) {
try {
return new MyClassLoader(new URL(String.format("jar:file:%s!/", libraryJar.getName())));
} catch(MalformedURLException e) {
throw new IllegalArgumentException(String.format("Path to module jar could not be converted into proper URL: %s", e.getMessage()));
}
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if(moduleClasses.contains(name)) {
Class<?> clazz = findLoadedClass(name);
if(clazz != null) {
return clazz;
} else {
return findClass(name);
}
} else {
return mainClassLoader.loadClass(name);
}
}
}
I'm asking because I'm totally not sure I've done the right thing. I'm using Eclipse for a web project. Let's call it WebProject (duh) in the package com.web.project.
I want WebProject to load JAR plugins at runtime, so I thought I could take advantage of java.util.ServiceLoader. So I created an interface com.web.project.WebProjectPlugin in the WebProject project with all the methods the plugins must implement.
Then I created the project PluginProject, adding WebProbject/build/classes in its Build path as a class folder:
package com.web.project.plugin;
import com.web.project.WebProjectPlugin;
public class TestPlugin implements WebProjectPlugin {
// Implementation of the interface methods...
}
Then I created a META-INF/services folder in the plugin project, put the text file com.web.project.WebProjectPlugin inside, containing the sole line "com.web.project.plugin.TestPlugin".
I exported the JAR file checking out the added build/classes folder and put it somewhere in the hard drive. When WebProject starts up, it does the following:
File[] jlist = pluginsDir.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.getPath().toLowerCase().endsWith(".jar");
}
});
URL[] urls = new URL[jlist.length];
for (int i = 0; i < jlist.length; i++)
urls[i] = jlist[i].toURI().toURL();
URLClassLoader ucl = new URLClassLoader(urls);
ServiceLoader<WebProjectPlugin> srvl =
ServiceLoader.load(WebProjectPlugin.class, ucl);
Iterator<WebProjectPlugin> iter = srvl.iterator();
while (iter.hasNext()) {
WebProjectPlugin plugin = iter.next();
plugins.add(plugin);
}
pluginsDir is a File object pointing to the directory the JAR file is in. At first it seems that srvl does its job, since iter isn't empty, but then it throws the dreaded NoClassDefFoundError when it reaches iter.next().
I've already managed to create a plugin manager project to test ServiceLoader, and it runs just fine, but it's a plain console Java application, not a web project. So, what am I doing wrong here?
I'm a little puzzled: how can it not find the class definition for com.web.project.WebProjectPlugin, since it's in the same project that is running? Has that something to do with the URLClassLoader object I'm using?
This is the stack trace.
Try assigning the parent classloader to your URLClassLoader
URLClassLoader loader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
A WebProject expects a certain hierarchy of class loaders, so it might be that your classes are not visible to each other if the parent/child hieararchy is not set properly.
I have wrote some code to compile a Java source code. It then produces the .class file. The problem is how do I run it?
For example, I am ok with the name of the program and class being set, I've used prog p = new prog(), in this case, however, the class file does not yet exist until I compile it. Not really sure what to do. Can someone give me an advice?
btw, the class looks like this:
public void compile{
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int compilationResult = compiler.run(null, null, null, fileToCompile);
}
public void run(){
Prog prog = new Prog();
prog.run();
}
If you just want to run it, you could launch a java process using
Runtime.exec or ProcessBuilder. These will create a seperate java process to run your java program. This is more likely what you want. You can essentially do the equivelant of:
>java someClass
from within your application. This link may help.
If you want to actually load the classfile and use it in your current application, I think something along the lines of this, or dynamically loading Java Classes ought to help. Basically (directly from the link, slightly modified):
public class MainClass {
public static void main(String[] args){
ClassLoader classLoader = MainClass.class.getClassLoader();
try {
Class aClass = classLoader.loadClass("MyClass");
System.out.println("aClass.getName() = " + aClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
Once you loaded the class, you have a Class object, and you can create an instance of the class represented by aClass by calling aClass.newInstance(), which is like
MyClass newObj = new MyClass()
Or you can use any of the other methods the Class object exposes.
As pointed out by davmac, the code sample above presumes that the code you're loading is on your applications classpath. If the class files you want to run are not in your classpath, you might want to look into URLClassLoader
Load it by URLClassLoader.
File root = new File("/java"); // The package root.
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { root.toURI().toURL() });
Class<?> cls = Class.forName("test.Test", true, classLoader); // Assuming package test and class Test.
Object instance = cls.newInstance();
// ...
See also:
How do I instantiate a class dynamically in Java?
You need to create a classloader (a URLClassLoader will probably be fine) which will load the just-compiled class file. (So for a URLClassLoader, the compilation output path should be one of the URLs).
Then, load the compiled class using the classloader, and execute it using reflection.
Class c = cl.loadClass("ClassName");
... etc.