Let us have a Groovy/Java application that should use a set of classes, defined in external *.jar-files (suppose they are located near the main executable jar).
So, the main class (let us call it Main) should load plugin.jar file at runtime and call some instance method on the class, defined in that jar (for some convention, suppose the class has the name as its jar - Plugin in our case).
The Main class could not know which plugins it has until it is runned. Let's throw away the CLASSPATH and java -jar run arguments and just do the magic with code only.
So, how this could be done and how the plugin.jar should be created (using Eclipse in my case) in order to be correctly loaded?
PS: yeah, i do compile my groovy sources into jar file. But i need to perform class loading and invoke exactly on-the-fly.
The secret was really simple!
Using URLClassLoader does the trick.
So, Groovy code:
ClassLoader loader = new URLClassLoader((URL[]) [
new File("C:\\Users\\errorist\\workspace\\javatest1\\bin\\").toURI().toURL()
])
Class c = loader.loadClass("src.SomeClass1")
c.invokeMethod("main", (String[]) ["Hello", "World"])
And the Java one:
File file = new File("C:\\Users\\errorist\\workspace\\javatest1\\bin\\");
URL[] urls = new URL[] { file.toURI().toURL() };
ClassLoader loader = new URLClassLoader(urls);
Class c = loader.loadClass("src.SomeClass1");
c.invokeMethod("main", new String[] { "Hello", "World!" });
The OSGi framework supports dynamic loading of plug-ins. There are multiple implementations, including Equinox, which underpins Eclipse itself.
Related
I'm having a hard time setting the classpath for a directory to a package of classes. I'm trying to run a jar file that takes a directory as a command line argument. The program uses the directory to access class files in a folder and uses reflection to explore the class fields and methods.
final File folder = new File(args[0]);
classList = dirParse.listFilesForFolder(folder);
I then go through the classList, get the name of each class, and use the Class.forName() method to access the classes.
Class c = Class.forName(className);
For the line above to work, I have to set the classpath to the address of the directory containing the classes.
I can get the program to run just fine when I'm using a directory of classes that do not belong to a package like below:
java -cp "Explorer.jar:/Users/john/Desktop/TestClass/" explorer.ExplorerDemo /Users/john/Desktop/TestClass/
However, for the following line, monopoly is a package and the program throws a ClassNotFoundException after calling Class.forName(className)
java -cp "Explorer.jar:/Users/john/Desktop/Programming\ Project/Monopoly/build/classes/monopoly/" explorer.ExplorerDemo /Users/john/Desktop/Programming\ Project/Monopoly/build/classes/monopoly/
For testing purposes, I tried adjusting `Class.forName() call to include the package name like below:
Class c = Class.forName("monopoly."+className);
However, this also throws ClassNotFoundException.
Class.forName is a shortcut to obtaining class information within the context of ClassLoader of the current class. Javadoc states that this is equivalent to
Class.forName("Foo", true, this.getClass().getClassLoader())
Provided that you class directory is supplied as runtime parameter and is not part of the original classpath, I would suggest you instantiating custom URLClassLoader instance that will be pointing to your directory.
Sample code:
public class ReflectionClassAnalysis {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
// URLClassLoader supports both directories and jar files
Path directory = Paths.get("/some/directory/");
Path jar = Paths.get("/some/binary.jar");
// You may be interested in providing parent ClassLoader for your new instance
// You can either use current class ClassLoader like
ClassLoader contextClassLoader = ReflectionClassAnalysis.class.getClassLoader();
// or current thread ClassLoader
// ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
URLClassLoader myClassLoader = new URLClassLoader(
new URL[]{
directory.toUri().toURL(),
jar.toUri().toURL()
},
contextClassLoader
);
// You may use ClassLoader directly to load class meta
Class<?> externalClass = myClassLoader.loadClass("your.class.name");
// or supply ClassLoader to forName method
// Class.forName("your.class.name", true, myClassLoader);
// Do your class analysis here
}
}
For JAR with classpath instructions please refer to: Run a JAR file from the command line and specify classpath
I am currently loading Java classes using Class.forName() to load it.
clazz = Class.forName("interfaces.MyClass");
But now I want to load classes from different directory, I have tried to set classpath by
clazz = Class.forName("-cp \"C:/dir\" distantinterfaces.DistantClass");
With no success and ClassNotFoundException. Full path to distant class is:
C:/dir/distantinterfaces/DistantClass.class
Use an URLClassLoader for this. The code might be something along the lines of:
File f = new File("C:/dir");
URL[] cp = {f.toURI().toURL()};
URLClassLoader urlcl = new URLClassLoader(cp);
Class clazz = urlcl.loadClass("distantinterfaces.DistantClass");
Either the directory is in the classpath, and you can use Class.forName() (which only accepts fuly qualified name classes, and not -cp command line options), or it's not in the classpath and you should then use a custom class loader.
You're not saying what you really want to do (why are you loading classes dynamically), but your best bet is to have the directory in the classpath.
You have to create an instance of ClassLoader which is aware of the directory with classes. See stackoverflow questions tagged urlclassloader.
I'm trying to figure out how to load two co-dependent Groovy scripts in java at runtime. If I have two groovy scripts like:
A.groovy
import B
class A {
A() {
B b = new B()
}
}
B.groovy
import A
class B {
B() {
A a = new A()
}
}
I would like to load them as java classes, but when I run:
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
loader.parseClass(new File("A.groovy"));
I get the error:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
A.groovy: 1: unable to resolve class B
# line 1, column 1.
import B
I certainly understand the reason for the error, but is there any way to load these classes at runtime despite their co-dependency? Thanks!
GroovyClassLoader must be enabled to find B.groovy on the classpath. Normally that means you change the classpath of you application to include the root of the scripts. Since there is no package here for the scripts and since you use new File("A.groovy"), I would assume that it is here the current directory.
If you don't want to change the classpath of the application, you can also call addURL to add the path containing the scripts.
One more thing to mention.... parseClass will always create a newly parsed class. You might want to try a standard loadClass call instead to avoid compiling the file multiple times. But of course that works only after you fixed the lookup for GroovyClassLoader, because using loadClass, GroovyClassLoader will also have to look for A.groovy in the same manner it does have to look for B.groovy
I wanted to add a system of adding more "commands" to my program from out side of it.
Basically, a folder that would be in the same directory as my jar (executable) that can have a varying number of "extensions"
Each "extension" would be a .class file that extends a certain abstract class that has methods defined for identification, etc.
However, I can't seem to find out if it's even possible to control these .class files from outside my main package.
Is this possible? If so how? And if not is there an alternative I could try?
Thanks!
As far as I understand you. You want to build a plugin system.
If so you should think about the java service provider approach.
In this approach you define a service provider interface (SPI) in your main
package and use the java META-INF/services location to lookup implementations.
In this case you can add extensions just by putting a jar file on the classpath.
As of java 1.6 you can use java.util.ServiceLoader.
Or you take a look at apache discovery
http://commons.apache.org/proper/commons-discovery/apidocs/org/apache/commons/discovery/tools/Service.html.
It's more powerful than the java.util.SerivceLoader since it lets you pass constructor arguments the the services it instantiates.
A possible plugin loader implementation could look like:
public interface ServiceInterface {
}
public class PluginHost {
public ServiceLoader<ServiceInterface> loadPlugins() {
File[] pluginLibraries = getPluginLibraries();
URL[] pluginLibUrls = new URL[pluginLibraries.length];
for (int i = 0; i < pluginLibUrls.length; i++) {
try {
pluginLibUrls[i] = pluginLibraries[i].toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException("Unable to load plugin: "
+ pluginLibraries[i], e);
}
}
URLClassLoader pluginsClassLoader = new URLClassLoader(pluginLibUrls,
ServiceInterface.class.getClassLoader());
ServiceLoader<ServiceInterface> serviceLoader = ServiceLoader.load(
ServiceInterface.class, pluginsClassLoader);
return serviceLoader;
}
private File[] getPluginLibraries() {
// please implement
}
}
It's possible.
Jars are just zipped class files.
As long as the class files are in the classpath you're fine.
Unzip a jar file (rename from .jar to .zip and unzip) to see what the directory structure should be for the java packages.
Sure you can load a class at runtime and create a Class object also:
See this Class#forName(String, boolean, ClassLoader) API:
See this tutorial for examples.
For a simple extension mechanism have a look at ServiceLoader.
For a more complete extension mechanism have a look at OSGi.
There are several posts about how to add jar-file to classpath at runtime by following idea:
- get current system classpath;
- suppose it is URLClassLoader;
- use reflection to set access for protected addURL method;
- use mentioned method to add url to classpath.
Here is an example:
Adding files to java classpath at runtime
Because of 2 and 3 steps this looks like "nasty hack".
How could I extend URLClassLoader and set it as a current? I am bit confused with classloaders and tried the following:
public static void main(String... args) {
URLClassLoader loader = new URLClassLoader(new URL[]{new URL("file:jxl.jar")});
System.out.println(loader.loadClass("jxl.Workbook"));
Thread.currentThread().setContextClassLoader(loader);
System.out.println(Class.forName("jxl.Workbook"));
} // main
I get ClassNotFoundException on the fourth line, while second works ok. (why it is so, by the way?)
The Class.forName method uses the "defining class loader of the current class," not the thread context classloader. In your case the ClassLoader that Class.forName will use is the one that loaded your application, i.e. the system class loader. This is a class loader that looks for resources in the class path.