Instantiating an external .jar at runtime?

Hello again; thanks to everyone who helped me with the multiple JOGL threads thing; it’s put my project back on track.
I just asked this question on sun’s java forum and they seem less than friendly over there for some reason?

Anyway, during my last discussion with my project supervisor we discussed the possibility of external .jar objects being run within the program; kind of like ‘plugins’.

These objects would have to have a void initialise(GL gl) and a void render(GL gl) function; the idea being an end-user of the software could create their own graphical objects, gauges etc as .jar files which could be accessed by the software without having to recompile it.

Is this possible to do, and if so how/where do I need to be looking?

Class.forName() would be a good starting point. Define an interface for your plugins, then put the plugins on the classpath. Specify the plugin class names in a data file somewhere, then use forName() to convert that to a class and Class.newInstance() to create an instance. After that it just becomes a regular interface you can use.

That’s the simple/low tech solution. At the other end of the scale you start writing your own custom classloaders and load the plugin jars at runtime. A lot of effort to do manually, although there are frameworks like OSGi which could save you a bunch of work.

URLClassLoader handles it nicely for you.


public static ClassLoader loadFancyPlugin(File jar) throws EverythingThatMightGoWrong
{
   return new URLClassLoader(new URL[]{ jar.toURI().toURL() });
}

Then use theLoader.loadClass(String) to lookup your class, instead of Class.forName(String)

Right, I’ve hit a bit of a hurdle with this;

As each external class is instantiated, I’m trying to add it to a Vector so that each one can be rendered in turn.
How do I do this, or is it possible?

Basically each of these external classes implements an interface ‘Component’.

I have tried creating a Vector and adding them using various ClassLoaders and things to no success?

What is the problem you’re having? Don’t you just need to cast?


if( Component.class.isAssignableFrom( loadedClass ) )
{
   Component instance = ( Component ) loadedClass.newInstance();
}

The problem I’m having is that I’m being a complete plebtard. :-\

Casting has solved my problem; thanks!

Right, every time I try to load using the following command:
Telemetry_Components.add((Component) ClassLoader.getSystemClassLoader().loadClass(jarFile).newInstance());

it throws a class not found exception.

Telemetry_Components is a vector
jarFile is a string of the filename (I’ve tried with and without .jar at the end).
The .jar file is located in the root folder of the project, where all the (working) openGL assets, textures etc are.

How do I fix this?

Sorry if this sounds completely n00bish; which it effectively is as this is the first time I’ve ever done it.

What Riven said. Use that code to construct a classloader, and then load the desired class from that classloader. The only tricky bit is that you need to know the name of the class you’re trying to load. Either have a naming convention, read the classname from some other source, or poke around in the jar using the java.util.jar stuff to try and find the class you want.

You need to specify the name of the Class, not of the jar file in loadClass(). For this to succeed, you need to have the jar in the runtime classpath of your application (as specified by the CLASSPATH environment variable or the -cp commandline switch)

If you need to load the jar dynamically, you have to use a new ClassLoader (like Riven already posted) instance to load it yourself:


// you could specify all jars in a plugins folder here
ClassLoader pluginClassLoader = new URLClassLoader(new URL[]{ jarFile.toURI().toURL() });
telemetryComponents.add((Component) pluginClassLoader.loadClass(pluginClassname).newInstance());

The problem here might be to know, how the plugin class is named. They either have to be specified (a jars manifest is the common place to do so) or you have to scan a jar for all Classes implementing your Component interface.

Scanning the manifest for specified Components


	ArrayList<Component> telemetryComponents = new ArrayList<Component>();
	JarFile pluginJar = new JarFile(pluginFile);
	ClassLoader pluginClassLoader = new URLClassLoader(new URL[] { pluginFile.toURI().toURL() });

	Manifest manifest = pluginJar.getManifest();
	// get the ; separated list of component classes from the manifest main section
	String componentList = manifest.getMainAttributes().getValue("Component-Classes");
	if (componentList != null)
	{
		String[] components = componentList.split("[; ]+");
		for (int i = 0; i < components.length; i++)
		{
			// you might need to handle some Exceptions here
			telemetryComponents.add((Component) pluginClassLoader.loadClass(components[i]).newInstance());
		}
	}

You need to have a Manifest in that jar like


Manifest-Version: 1.0
Created-By: YOU
Component-Classes: org.yourorg.TelemetryComponent1;org.yourorg.TelemetryComponent2

Scanning the jar for classes implementing the Component interface


	ArrayList<Component> telemetryComponents = new ArrayList<Component>();
	JarFile pluginJar = new JarFile(pluginFile);
	ClassLoader pluginClassLoader = new URLClassLoader(new URL[] { pluginFile.toURI().toURL() });

	// Iterate over the jar entries to find Component classes
	for (Enumeration entries = pluginJar.entries(); entries.hasMoreElements();)
	{
		JarEntry entry = (JarEntry) entries.nextElement();
		String candidateName = entry.getName();
		// only look at (non-inner) classes
		if(candidateName.endsWith(".class") && candidateName.indexOf('$') == -1)
		{
			// produce a class name by replacing / and removing the suffix
			candidateName = candidateName.replace('/', '.').substring(0,candidateName.length()-6);
			// you might need to handle some Exceptions here
			Class candidate = pluginClassLoader.loadClass(candidateName);
			// if the loaded class is a Component, add an instance to the list
			if(Component.class.isAssignableFrom(candidate))
			{
				telemetryComponents.add((Component) candidate.newInstance());
			}
		}
	}

The latter has the advantage, that you only need to implement the right interface (no Manifest editing), but also has the disadvantage of loading all classes right away, which circumvents the lazy classloading normally implemented by classloaders.

k, from everyone’s advice I’ve put together this: (Basically sent .jar file ‘fileName’ and Class name ‘className’ and reference to Vector to add to)

private boolean loadClass(Vector vector, String fileName, String className)
{
System.out.println(“Attempting to load component…”);

File pluginJar = new File(fileName);
URLClassLoader pluginClassLoader;
try
{
	pluginClassLoader = new URLClassLoader(new URL[] { pluginJar.toURI().toURL() });

	try
	{
		vector.add((Component) pluginClassLoader.loadClass(className).newInstance());
	}
	catch (InstantiationException e)
	{
		System.out.println("FAIL: Instantiation exception");
		return false;
	}
	catch (IllegalAccessException e)
	{
		System.out.println("FAIL: Illegal access!");
		return false;
	}
	catch (ClassNotFoundException e)
	{
		System.out.println("FAIL: Class not found!");
		return false;
	}
}
catch (MalformedURLException e)
{
	System.out.println("FAIL: The URL is apparently malformed");
	return false;
}
	
System.out.println("Woo and yay the component hath loadeth!");
	
return true;

}

This always throws a class not found exception - can anyone see the problem? (The .jar is located in the project directory)

What are the arguments you pass to your loadClass(…, …, …) function

Vector vector, in this case ‘Telemetry_Components’ (Reference to the vector that will store the component).
String fileName, in this case “306_Speedo.jar” (the name of the jar file in which the class is located).
String className, in this case “Gauge” (The name of the class to be instantiated).

Is “Gauge” the fully qualified name? So is Gauge in the default package?

Just to clarify for the OP:

if your class Gauge is in the package: my.game.plugins, then your fully qualified classname is: my.game.plugins.Gauge, which should be used as the ‘className’ argument for your method call, and stored in the JAR as: “my/game/plugins/Gauge.class”.

Personally I’d use ServiceLoader - it’s a bit more effort when creating your plugin jars, but it prevents you having to play around with custom classloaders or suchlike.

The classes can be created by someone else but will implement my ‘component’ interface. They would then be located (via a Components.cfg file specifying the .jar file and class name) by the application after it has started to execute.
They will then (ideally) be put with the rest of the resources when the application is distributed.

I’m not 100% sure what you mean by qualified class; they won’t necessarily be accessable to the IDE I’m using if that’s what you mean?

A full qualified class name is the name of the class including the package name where it’s located in the jar in dot-notation. Riven has given a good explanation - frankly I don’t know what to write to make it more clear…

Normally a class looks similar to this:


package org.some.package;

import java.util.*;
import org.some.package.Component;

public class Gauge extends Component
{
//...
}

The full qualified name of the class would be “org.some.package.Gauge”. This is the name a classloader needs to find the class in the jar. Additionally the class has to be stored in the directory “org/some/package” in the jar.

That has fixed it and it’s now fully working; it was in package External_Components.Gauge.

Thanks for everyone’s help.

Just worth a warning - using capital letters in package names is BAD.

Not only is it against the sun naming conventions, but it can also confuse some compilers & IDEs.