Load a class to Java classpath dynamically using Eclipse and OSGi - java

Combining the information from many posts on this site and many others, I got the following code to dynamically add (at run time) a directory containing classes to the classpath and load a class within that directory.
I'm using OSGi bundles and running from eclipse an "Eclipse Application" (a kind of Run Configuration).
This is the code I'm using:
CASE 1: (both cases are different things I've tried to do the same thing.)
File file = new File("/Users/alek/fastFIX/myJPass/");
URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};
ClassLoader cl = new URLClassLoader(urls);
Class cls = cl.loadClass("GuiLauncher"); //the file GuiLauncher.class is in the /Users/alek/fastFIX/myJPass/ directory
Class[] argTypes = new Class[] { String[].class };
Method main = cls.getDeclaredMethod("main", argTypes); //trying to run the main class
main.invoke(null, (Object) args);
I don't get any error, and nothing happens.
I've also tryied the following, as I actually need the loaded class to interact with other (already loaded) classes.
CASE 2:
ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { new File("/Users/alek/fastFIX/myJPass/").toURL() }, currentThreadClassLoader);
Thread.currentThread().setContextClassLoader(urlClassLoader);
then i load like this:
Class<?> c = Class.forName("GuiLauncher");
or like this:
Class<?> c = Thread.currentThread().getContextClassLoader().loadClass("GuiLauncher");
and try to invoke the main function like this:
Class[] argTypes = new Class[] { String[].class };
Method main = cls.getDeclaredMethod("main", argTypes); //trying to run the main class
main.invoke(null, (Object) args);
here also nothing happens.
Any clue of what could be happening? I've read all related posts here and many places else with no luck.

In OSGI framework it is necessary to add the OSGI class loader as a parent, something like this:
...
ClassLoader cl = new URLClassLoader(new URL[]{file.toURI().toURL()}, this.getClass().getClassLoader());
...

In case 1, I suspect that the GuiLauncher class is already on the classpath, so may get loaded by the default classloader. Try doing Class.forName() before setting up the dynamic classloader, to confirm that there's no class available. If you are in Eclipse, you need to be careful that the class is not included on the Eclipse classpath, which is what would normally happen. You might need to compile it once then move the .java and .class files elsewhere to hide them from Eclipse!
In case 2:
Class.forName("GuiLauncher");
will not work as you expect, because this will use the system classloader. This should fail, hence my suspicion above. You need use the other version of this method that specifies your dynamic classloader:
Class.forName("GuiLauncher", true, urlClassLoader)
The following code works for me.
import java.net.*;
import java.lang.reflect.*;
import java.io.File;
public class Main{
public static void main(String[] args)
{
try{
Class cls = Class.forName("Plugin");
}
catch(Exception e){
System.out.println("Nothing there!");
}
try{
File file = new File("plugin");
ClassLoader cl = new URLClassLoader(new URL[]{file.toURI().toURL()});
Class cls = Class.forName("Plugin", true, cl);
Method main = cls.getDeclaredMethod("main", new Class[] { String[].class });
main.invoke(null, (Object) args);
}
catch(Exception e){
e.printStackTrace();
}
}
}
The Plugin class is compiled in the plugin subfolder, so it's not on the classpath used to run Main, as shown by the first Class.forName().
public class Plugin{
public static void main(String[] args)
{
System.out.println("Plugin was invoked!");
}
}
and prints out:
Nothing there!
Plugin was invoked!

Related

Java 9 - add jar dynamically at runtime

I've got a classloader problem with Java 9.
This code worked with previous Java versions:
private static void addNewURL(URL u) throws IOException {
final Class[] newParameters = new Class[]{URL.class};
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class newClass = URLClassLoader.class;
try {
Method method = newClass.getDeclaredMethod("addNewURL", newParameters );
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u});
} catch (Throwable t) {
throw new IOException("Error, could not add URL to system classloader");
}
}
From this thread I learned that this has to be replaced by something like this:
Class.forName(classpath, true, loader);
loader = URLClassLoader.newInstance(
new URL[]{u},
MyClass.class.getClassLoader()
MyClass is the class I'm trying to implement the Class.forName() method in.
u = file:/C:/Users/SomeUser/Projects/MyTool/plugins/myNodes/myOwn-nodes-1.6.jar
String classpath = URLClassLoader.getSystemResource("plugins/myNodes/myOwn-nodes-1.6.jar").toString();
For some reason - I really can't figure out, why - I get a ClassNotFoundException when running Class.forName(classpath, true, loader);
Does someone know what I'm doing wrong?
From the documentation of the Class.forName(String name, boolean initialize, ClassLoader loader) :-
throws ClassNotFoundException - if the class cannot be located by the specified class loader
Also, note the arguments used for the API includes the name of the class using which the classloader returns the object of the class.
Given the fully qualified name for a class or interface (in the same format returned by getName) this method attempts to locate, load, and link the class or interface.
In your sample code, this can be redressed to something like :
// Constructing a URL form the path to JAR
URL u = new URL("file:/C:/Users/SomeUser/Projects/MyTool/plugins/myNodes/myOwn-nodes-1.6.jar");
// Creating an instance of URLClassloader using the above URL and parent classloader
ClassLoader loader = URLClassLoader.newInstance(new URL[]{u}, MyClass.class.getClassLoader());
// Returns the class object
Class<?> yourMainClass = Class.forName("MainClassOfJar", true, loader);
where MainClassOfJar in the above code shall be replaced by the main class of the JAR myOwn-nodes-1.6.jar.

Reload used classes at runtime Java

I'm working on a program that watches a directory and runs all tests in the directory when it sees changes in the directory.
This requires the program to dynamically load the classes, instead of getting the cached copies.
I can dynamically load the test classes. Changes to the tests get detected and used at runtime. However, this isn't the case for the classes tested by the tests.
My code for dynamically loading the classes and returning a list of test classes:
List<Class<?>> classes = new ArrayList<Class<?>>();
for (File file : classFiles) {
String fullName = file.getPath();
String name = fullName.substring(fullName.indexOf("bin")+4)
.replace('/', '.')
.replace('\\', '.');
name = name.substring(0, name.length() - 6);
tempClass = new DynamicClassLoader(Thread.currentThread().getContextClassLoader()).findClass(name) } catch (ClassNotFoundException e1) {
// TODO Decide how to handle exception
e1.printStackTrace();
}
boolean cHasTestMethods = false;
for(Method method: tempClass.getMethods()){
if(method.isAnnotationPresent(Test.class)){
cHasTestMethods = true;
break;
}
}
if (!Modifier.isAbstract(cachedClass.getModifiers()) && cHasTestMethods) {
classes.add(tempClass);
}
}
return classes;
with DynamicClassLoader being as the Reloader described here How to force Java to reload class upon instantiation?
Any idea how to fix it? I thought all classes would be dynamically loaded. Note however that I don't overwrite loadclass in my DynamicClassLoader because if I do my test classes give init
EDIT:
This doesn't work, the class gets loaded but the tests in it aren't detected...
List<Request> requests = new ArrayList<Request>();
for (File file : classFiles) {
String fullName = file.getPath();
String name = fullName.substring(fullName.indexOf("bin")+4)
.replace('/', '.')
.replace('\\', '.');
name = name.substring(0, name.length() - 6);
Class<?> cachedClass = null;
Class<?> dynamicClass = null;
try {
cachedClass = Class.forName(name);
URL[] urls={ cachedClass.getProtectionDomain().getCodeSource().getLocation() };
ClassLoader delegateParent = cachedClass .getClassLoader().getParent();
URLClassLoader cl = new URLClassLoader(urls, delegateParent) ;
dynamicClass = cl.loadClass(name);
System.out.println(dynamicClass);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Edit edit: i detect the test methods like this:
for(Method method: dynamicClass.getMethods()){
if(method.isAnnotationPresent(Test.class)){
requests.add(Request.method(dynamicClass, method.getName()));
}
}
If you used the custom ClassLoader exactly like in the linked answer it is not overriding the method protected Class<?> loadClass(String name, boolean resolve). This implies that when the JVM is resolving dependencies it will still delegate to the parent class loader. And, of course, when it was not delegating to the parent ClassLoader it had the risk of missing some required classes.
The easiest solution is to set up the right parent class loader. You are currently passing Thread.currentThread().getContextClassLoader() which is a bit strange as your main intention is that the delegation should not delegate to that loader but load the changed classes. You have to think about which class loaders exist and which to use and which not. E.g. if the class Foo is within the scope of your current code but you want to (re)load it with the new ClassLoader, Foo.class.getClassLoader().getParent() would be the right delegate parent for the new ClassLoader. Note that it might be null but this doesn’t matter as in this case it would use the bootstrap loader which is the correct parent then.
Note that when you set up the right parent ClassLoader matching your intentions you don’t need that custom ClassLoader anymore. The default implementation (see URLClassLoader) already does the right thing. And with current Java versions it is Closeable making it even more suitable for dynamic loading scenarios.
Here is a simple example of a class reloading:
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
public class ReloadMyClass
{
public static void main(String[] args)
throws ClassNotFoundException, IOException {
Class<?> myClass=ReloadMyClass.class;
System.out.printf("my class is Class#%x%n", myClass.hashCode());
System.out.println("reloading");
URL[] urls={ myClass.getProtectionDomain().getCodeSource().getLocation() };
ClassLoader delegateParent = myClass.getClassLoader().getParent();
try(URLClassLoader cl=new URLClassLoader(urls, delegateParent)) {
Class<?> reloaded=cl.loadClass(myClass.getName());
System.out.printf("reloaded my class: Class#%x%n", reloaded.hashCode());
System.out.println("Different classes: "+(myClass!=reloaded));
}
}
}

Why isn't my class being loaded through the external class loader?

I want to run the constructor of the Main.class in the package Test2, located in the folder C:\classes\
This is the code I'm using. It throws a class not found exception when it tries to turn it into a class. And then once it's part of the class object, will the constructor automatically be run, or do I have to instance it somehow? Test2 is inputted into this code as text.
if (Main.os.equals("Windows"))
{
String path = "C:\\classes\\";
}
else
{
String path = "~/classes/";
}
File file = new File(path);
try
{
URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};
Main.print("Stage 1");
ClassLoader cl = new URLClassLoader(urls);
Main.print("Stage 2");
Class cls = cl.loadClass(text + ".Main");
Main.print(text + " was loaded into memory.");
close();
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
I suspect your problem is one of the following:
file doesn't exist or hasn't been properly specified. Check via file.exists()
Your class file is not located in the correct directory. If the package declaration for the Main class is package Test2; then your class file must be in the following location: C:\classes\Test2\Main.class.
If Main is nested class, then you will need to refer to the enclosing class when loading it, eg cl.loadClass("Test2.EnclosingClass$Main");
My guess it that your problem is number 2! :)
Good luck.
Oh, and yes, you'll need to create an instance of your object if you want the constructor to be called: clazz.newInstance() is the simplest method for no-args constructors.
Can you post the exact error message.
But here is how I execute a main method of using a class loader
urlLoader = new URLClassLoader(urls);
Class runClass = urlLoader.loadClass(classToRun);
System.out.println("Starting Program !!!");
Object[] arguments = new Object[]{args};
Method mainMethod = runClass.getMethod("main", new Class[] {args.getClass()});
mainMethod.invoke(null, arguments);
Note: classToRun will be the full package/class definition
i.e. net.sf.RecordEditor.edit.FullEditor
Note: I use it to load from jar files, it will be similar for directories
It is taken from the run class here
http://record-editor.svn.sourceforge.net/viewvc/record-editor/Source/RecordEditor/src/net/sf/RecordEditor/utils/Run.java?revision=65&view=markup
An example of calling the class is here
http://record-editor.svn.sourceforge.net/viewvc/record-editor/Source/RecordEditor/src/net/sf/RecordEditor/RunFullEditor.java?revision=65&view=markup

java.lang.ClassNotFoundException while attempting to load dynamically a class in glassfish

I'm trying to load dynamically a class contained in a .jar file. I know the whole class name and I know for sure that the class implements the interface AlgorithmClass.
My code looks like this:
addURLToSystemClassLoader(dir.toURI().toURL());
Class cl = Class.forName(algorithm.getClassName());
AlgorithmClass algorithmClass = (AlgorithmClass)cl.newInstance();
Where dir is the File object of the .jar file and addURLToSystemClassLoader(URL) looks like this:
private void addURLToSystemClassLoader(URL url) throws IntrospectionException {
URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> classLoaderClass = URLClassLoader.class;
try {
Method method = classLoaderClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(systemClassLoader, new Object[]{url});
} catch (Throwable t) {
t.printStackTrace();
throw new IntrospectionException("Error when adding url to system ClassLoader ");
}
}
I checked and the URL is being added to the class loader.
When I try to get the Class object I get the error:
SEVERE: javax.servlet.ServletException: java.lang.ClassNotFoundException: id3.Algorithm
(id3.Algorithm is the full name of the class I'm trying to load)
I've tried creating a new ClassLoader like below:
ClassLoader cload = new URLClassLoader(new URL[]{dir.toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class cl = Class.forName(algorithm.getClassName(), false, cload);
AlgorithmClass algorithmClass = (AlgorithmClass)cl.newInstance();
But then I get the error:
java.lang.NoClassDefFoundError: lib/algorithm/AlgorithmClass
I've tried creating a new URLClassLoader with all the URLs that the system class loader has but the effect was the same.
The "worst" part of this is that both ways are working perfectly fine on the jUnit test that I have for testing this part of my code.
I'm using Glassfish 3.1.1 as my app server.
dir shouldn't contain 'lib'.
Try this:
ClassLoader cload = new URLClassLoader(new URL[]{dir.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
Class cl = Class.forName(algorithm.getClassName(), true, cload);
AlgorithmClass algorithmClass = (AlgorithmClass)cl.newInstance();
You have class-loading issue. You shoud be aware that your addURLToSystemClassLoader() is actually the heck...
Put your jar to the classpath. Use Class.forName() idiom. If it fails use version that receives ClassLoader as parammeter namely
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
and path Thread.currentThread().getContextClassLoader() as ClassLoader parameter.
See also my another answer below.

Can a directory be added to the class path at runtime?

In order to better understand how things works in Java, I'd like to know if I can dynamically add, at runtime, a directory to the class path.
For example, if I launch a .jar using "java -jar mycp.jar" and output the java.class.path property, I may get:
java.class.path: '.:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java'
Now can I modify this class path at runtime to add another directory? (for example before making the first call to a class using a .jar located in that directory I want to add).
You can use the following method:
URLClassLoader.addURL(URL url)
But you'll need to do this with reflection since the method is protected:
public static void addPath(String s) throws Exception {
File f = new File(s);
URL u = f.toURL();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u});
}
See the Java Trail on Reflection. Especially the section Drawbacks of Reflection
Update 2014: this is the code from the accepted answer, by Jonathan Spooner from 2011, slightly rewritten to have Eclipse's validators no longer create warnings (deprecation, rawtypes)
//need to do add path to Classpath with reflection since the URLClassLoader.addURL(URL url) method is protected:
public static void addPath(String s) throws Exception {
File f = new File(s);
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u.toURL()});
}
Yes, you can use URLClassLoader.. see example here. Doesn't use reflection.
-- edit --
Copying example from the link as suggested.
import javax.naming.*;
import java.util.Hashtable;
import java.net.URLClassLoader;
import java.net.URL;
import java.net.MalformedURLException;
public class ChangeLoader {
public static void main(String[] args) throws MalformedURLException {
if (args.length != 1) {
System.err.println("usage: java ChangeLoader codebase_url");
System.exit(-1);
}
String url = args[0];
ClassLoader prevCl = Thread.currentThread().getContextClassLoader();
// Create class loader using given codebase
// Use prevCl as parent to maintain current visibility
ClassLoader urlCl = URLClassLoader.newInstance(new URL[]{new URL(url)}, prevCl);
try {
// Save class loader so that we can restore later
Thread.currentThread().setContextClassLoader(urlCl);
// Expect that environment properties are in
// application resource file found at "url"
Context ctx = new InitialContext();
System.out.println(ctx.lookup("tutorial/report.txt"));
// Close context when no longer needed
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
} finally {
// Restore
Thread.currentThread().setContextClassLoader(prevCl);
}
}
}

Categories