Loading external Classes with external dependencies - URLClassLoader ClassNotFoundException - java

I am loading a Class from an external.JAR file and by means of URLClassLoader, which works as long as the external Class does not reference another JAR. If I do it yields a ClassNotFoundException.
As a workaround I add the other second tier JAR as a dependency, but I would like to load these also dynamically on runtime.
Question: How do I load an external Class which references other external classes? or how do I load external JAR files and classes, in
the correct order so I am not getting an exception?
Should I catch the exception and then "first" load that class that was not yet loaded?

You actually could load them all using a child first class loader. That main jar and all of its dependencies would be apart of that class loader and can be referenced and thrown away if needed.
Main Class Loader -> ChildFirstClassLoader -> Loads jar and dependent jars
This is a good example
Here is another SO similar reference
Semi Example
File file = new File("c:\\free-universe-games-llc\\app.jar");
URL url = file.toURI().toURL();
ChildFirstClassLoader urlClassLoader = new ChildFirstClassLoader(new URL[]{url}, SomeClassLoader.class.getClassLoader());
Class<?> aClass = urlClassLoader.loadClass("com.freeuniversegames.app.App.class");
Object o = aClass.newInstance();

Related

Externalize classes in multiple jars

So i've been working on a project of mine which works in modules. A module is simply an abstract class which all the modules extend from and replace the empty methods they need to. No modules have public functions that arent defined in the abstract class and each function is called depending on specific events.
What i'd like to do is externalize each module (for this exemple ill refer to Autorole as an exemple module) into its own jar that the main class will fetch and use. That would allow for Autorole to be hotswapped/hot updated by simply replacing it's old jar file with the new one and reload the main program (rescan the jars in the modules folder and load them, without turning off the main program)
Edit1 : Very much like craftBukkit loads it's plugins
Currently my approach is to export each module as an executable jar and start it with a command using the runtime environment of the main program. (java -jar Autorole.jar for eg)
The main function of each module simply adds an instance of their module (here Autorole extended from the module class) into a static list in the main class of the main jar. And it works fine, its able to execute the Autorole jar properly and use it like it used to when everything was in the same jar.
But here comes the problem.
Autorole depends on the main class and thus has it as a dependency, and i dont know how to make it able to recognize the main file as their dependency, meaning right now i need to have a copy of the main class for each module which is far from convenient or optimized.
What i wanted to try at first was specify the path the main file is in but i got stuck rather quickly. The path cant be hardcoded since the main file has versions and modules arent version specific, plus the name might not be the exact same as expected, so i thought of passing the path of the main jar as a java arg, and that's where i'm at as i dont know what to do once i have the string in hands
tl-dr:
How can i load a jar from another like craftBukkit loads it's plugins
The Class Loaders allow you to dynamically load classes into a JVM process. URLClassLoader is a class loader that loads classes from a given URL e.g. external jar on the file system.
Just call ClassLoader#loadClass(String name) with the fully qualified class name, e.g: Class<*> theClass = classLoader.loadClass("com.packagename.ClassName"); and you will receive a Class. Then you'll have to get a constructor which you want to use to create this class's instance, e.g. Constructor<*> theConstructor = theClass.getConstructor(String.class); Finally invoke the constructor using Constructor#newInstance(Object... initargs) providing arguments required by the constructor.
Most likely, you will also want to set your plugin's class loader as a parent class loader.
Full example:
URL[] jars = new URL[1];
jars[0] = new File("path/to/file.jar").toURI().toURL();
URLClassLoader classLoader = new URLClassLoader(jars, YourPlugin.java.getClassLoader());
Class<*> loadedClass = classLoader.loadClass("com.example.ClassName");
Object classInstance = loadedClass.getConstructor().newInstance();

Java resource from class vs Thread

what is the difference between
getClass().getResource("some-resource-file.txt")
vs
Thread.currentThread().getContextClassLoader().getResource("some-resource-file.txt")
I have resources in src/test/resources & I am trying to access them from Unit test. It's a typical maven style directory structure.
I was expecting both to behave identical. But it's not., getClass().getResource() doesn't fetch the resource where as from Thread I am able to fetch the resource.
So how do they differ ?
Let's say you're developing a library and the library jar is placed into a web container's classpath.
Now let's say a webapp, using this library, is deployed in the container.
The webapp will have its own class loader, using WEB-INF/classes and WEB-INF/lib/*.jar as its classpath. And the container, for each request coming to your webapp, will set the current thread classloader to the class loader of the classpath.
When your library code uses getClass().getResource(), it will load the resource using the classloader used to load the library classes. It will thus use the container's class loader, and will thus use the resources in your library's jar and in the other libraries used to start the container.
If your library code uses Thread.currentThread().getContextClassLoader() instead to load the resource, it will use the classloader associated with the current thread, and will thus load the resources from the webapp's class loader, looking for the resource in WEB-INF/classes and in the jars inside WEB-INF/lib.
The latter can be what you want. For example, if you're designing a logging library (please don't), the logger will be able to read a different configuration file for each webapp, instead of having a single config shared by all the webapps.
Regarding the way the two methods look for resources, they all finally delegate to a ClassLoader to load the resource. But loading it via a Class will treat relative paths as relative to the invoked class, whereas loading it via a ClassLoader expects a path starting at the root of the package tree. Suppose your class is in the package com.foo, then
MyClass.class.getResource("hello.txt")
is equivalent to
MyClass.class.getResource("/com/foo/hello.txt")
and is equivalent to
MyClass.class.getClassLoader().getResource("com/foo/hello.txt");
There is a special case getting the first class running (which is why you have to declare the main() method as static with an array of strings as an argument).
Once that class is loaded and is running, future attempts at loading classes are done by the class loader. At its simplest, a class loader creates a flat name space of class bodies that are referenced by a string name. Each class in Java uses own classloader to load other classes. So if ClassA.class references ClassB.class then ClassB needs to be on the classpath of the ClassLoader of ClassA, or its parents.
The thread context ClassLoader is a special one in that it is the current ClassLoader for the currently running thread. This is useful in multi-classloader environments. An object can be created from a class in ClassLoader C and then passed to a thread owned by ClassLoader D. In this case the object needs to use Thread.currentThread().getContextClassLoader() directly if it wants to load resources that are not available on its own ClassLoader .

ClassLoader class dependeny (plugin)

I want to ask about UrlClassLoaders. I write a main .jar file which works well. Now I want to make it able to load plugins from one of its folders. I wrote it until that it finds the .jar files in the folder, can list their classes inside (their name, path). The problem is when I ask him to find that class (for create an instance of it) they won't find the class which it inherites from my main jar file like this:
IN MAIN JAR:
-class Expansion { ... }
In a plugin jar:
-class ExpansionA extends MainJar.Expansion { ... }
So when I ask an UrlClassLoader to resolve the "ExpansionA" class it will search for the "Expansion" class in the plugin jar instead of the MainJar, and there is no "Expansion" class in the plugin jar because it inherites from the MainJar as a resource library... and throws NoDefFoundForClassError exception while the defineClass method. How can I tell him to check after the classes in the MainJar too? (Its not only about 1 class but I want the plugin to be able to inherite any class from the MainJar). Can u help me?
ClassLoaders have a parent to which load requests are delegated first. To ensure that the new class loader you are creating can access the class Expansion you should set the parent of the new ClassLoader to the ClassLoader of your class Expansion:
ClassLoader myLoader=newURLClassLoader(urls, Expansion.class.getClassLoader());
This does not only ensure that the Expansion class will be found by myLoader, it will also guaranty that it resolves to the same runtime class (as the parent is asked first). This is important if the plugin jar contains a copy of the class.

Java Classloader unable to load modules?

I seem to be having a problem with loading classes in a module loader for an application I'm developing. Basically, all classes I'm going to be loading with it extend another class, which is located in a package in the actual application. For our purposes, we'll call it Module. Modules are located in a separate folder outside the actual application.
The loader iterates through a folder and executes the loadFile() method on any file with the extension .class. All classes have the package declaration as the Module class, as well as the extends Module declaration in the class header.
This is the loadFile() method, header and exception clauses excluded:
String fileName = file.getName();
String className = fileName.replace(".class", ""); //Strips extension
Class<?> aClass = Class.forName(className, true, new URLClassLoader(new URL[] { file.toURI().toURL() }));
Class<? extends Module> modClass = aClass.asSubclass(Module.class);
return modClass.getConstructor().newInstance();
I keep getting a ClassNotFoundException on the third line. And past that, if it ClassNotFoundException weren't thown, would all dependencies be resolved?
From the documentation for URLCLassLoader:
This class loader is used to load classes and resources from a search path of URLs referring to
both JAR files and directories. Any URL that ends with a '/' is assumed to refer to a directory.
Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed.
So, you must use URLs for either directories or .jars
Two solutions:
Force your users to give you .jar files, including a manifest of some sort inside with the classname that they wish to be loaded. This approach is used by the Bukkit developers. Having used this method in the past, the dependencies should all be packaged in the .jar file and thus in the URLClassLoader's search path and able to be loaded.
Use the URL of the file's directory, and search that directory for .class files. I'm not sure if dependencies will be loaded using this method.
In the URLClassLoader, do not pass the file, but the parent folder. However, this works correctly if classes are all in the "default package", so the .class files you are loading must not have a package declaration on top.
By default, a class loader will also trigger loading of all classes required to properly build the class: it will try to load the super class, the super super class etc... all the interfaces and super interfaces, the classes needed for static fields and methods, the classes needed for method signatures (return types and parameters). It will not usually try to load classes used internally by methods, not until you execute those methods.
However, usually a class loader does not "contain" all those classes, for example your class will end up inheriting from java.lang.Object, and your URLClassLoader will not contain the Object.class file. So, class loaders delegate to their parent class loaders.
You are currently creating a URLClassLoader without specifying a parent, in Java 7 at least the parent will default to the "system class loader", which is fine as long as you are in a plain java application, and not executing your code itself inside a specific hierarchy of class loaders. If however you are running that code in a web application, or in an OSGI container etc.. the you should give the URLClassLoader a proper parent to delegate to, for example Thread.currentThread().getContextClassLoader() or this.getClass().getClassLoader().
I suppose you need all of this because you need to load those class dynamically at runtime.

jar-in-jar class loading issue

i followed the steps described at Create cross platform Java SWT Application . i create a jar which has a swt_browser.jar containing only my class using swt library. then i added other platrom-specific swt jars.
i use the below code to load the swt_browser.jar and the platform specific swt library jar. but somehow the call for loading class SWTBrowser complains :
java.lang.ClassNotFoundException: org.eclipse.swt.widgets.Layout
can you tell what i am doing wrong?
-----code for loading swt jars-------
ClassLoader parent = Main.class.getClassLoader();
URL.setURLStreamHandlerFactory(new RsrcURLStreamHandlerFactory(parent));
URL swtBrowserFileUrl = new URL("rsrc:swt_browser.jar");
URL swtFileUrl = new URL("rsrc:" + swtFileName);
ClassLoader cl = new URLClassLoader(new URL[]{swtBrowserFileUrl, swtFileUrl}, parent);
Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
addUrlMethod.invoke(cl, swtBrowserFileUrl);
addUrlMethod.invoke(cl, swtFileUrl);
Thread.currentThread().setContextClassLoader(cl);
try {
// Check we can now load the SWT class -this check passes!
Class.forName("org.eclipse.swt.widgets.Layout", true, cl);
} catch (ClassNotFoundException exx) {
System.err.println("Launch failed: Failed to load SWT class from jar: " + swtFileName);
throw new RuntimeException(exx);
}
//this line below throws exception : java.lang.ClassNotFoundException: org.eclipse.swt.widgets.Layout
Class<?> c = Class.forName("com.sun.star.google.gui.SWTBrowser", true, cl);
Object obj = c.newInstance();
Method run = c.getMethod("run", url.getClass()); //$NON-NLS-1$
run.invoke(obj, new Object[]{url});
Your jar has the following file structure:
You are setting up the following ClassLoaders:
Main classloader
SWT classloader
The Main classloader has all of the following on its classpath:
Your app classes
The jar-in-jar classloader
The SWT classloader is constructed at runtime and has the following on its classpath:
The SWT classes for your platform
Your loading code asks the SWT classloader to load an SWT class and that works. However, you then ask it to load an application class. It doesn't know about your application class so it delegates to its parent, the Main classloader. This manages to load your application class which then tries to reference an SWT class. This reference is handled by the classloader which loaded your application class - the Main classloader. This doesn't know about the SWT class and throws an exception.
You need to package your application differently. You need to have the following classloaders.
Main classloader
SWT classloader
The Main classloader has all of the following on its classpath:
A single app class which handles building the SWT classloader
The jar-in-jar classloader
The SWT classloader is constructed at runtime and has the following on its classpath:
The SWT classes for your platform
Your app classes
This means that when you load your app class it will be loaded by the SWT classloader. This means that when your app refers to SWT classes the correct classloader is used.
For a working example of this, you can download and examine the following jar: https://github.com/downloads/mchr3k/org.intrace/intrace-ui.jar
EDIT: The ant build file which produced intrace-ui.jar can be seen here: https://github.com/mchr3k/org.intrace/blob/master/org.intrace/build.xml
In particular, the "jar" target handles the packaging.

Categories

Resources