Many a times, we are required to load a jar file and its classes and then invoke its functions and all this is needed to be done dynamically. Such requirements often come when we are trying to develop a sort of plugin system. Thankfully Java has a rich support for doing such operations.

In this post we will look into how to do make a basic use of Java ClassLoader and Annotations to achieve this. Let us start by saying that we have a plugin in the form of a jar file that needs to be dynamically loaded at runtime in our program.

Below is a sample code for loading getting all the class names that are there in jar file or in a JarInputStream.

// Returns an arraylist of class names in a JarInputStream
private ArrayList<String> getClassNamesFromJar(JarInputStream jarFile) throws Exception {
	ArrayList<String> classNames = new ArrayList<>();
	try {
		//JarInputStream jarFile = new JarInputStream(jarFileStream);
		JarEntry jar;

		//Iterate through the contents of the jar file
		while (true) {
			jar = jarFile.getNextJarEntry();
			if (jar == null) {
				break;
			}
			//Pick file that has the extension of .class
			if ((jar.getName().endsWith(".class"))) {
				String className = jar.getName().replaceAll("/", "\\.");
				String myClass = className.substring(0, className.lastIndexOf('.'));
				classNames.add(myClass);
			}
		}
	} catch (Exception e) {
		throw new Exception("Error while getting class names from jar", e);
	}
	return classNames;
}

// Returns an arraylist of class names in a JarInputStream
// Calls the above function by converting the jar path to a stream
private ArrayList<String> getClassNamesFromJar(String jarPath) throws Exception {
	return getClassNamesFromJar(new JarInputStream(new FileInputStream(jarPath)));
}

Now if we have the method to get all the class names in a jar file, we can write a method to load all the classes in a jar file. Below is our sample code for that

// get an arraylist of all the loaded classes in a jar file
private ArrayList<Class> loadJarFile(String filePath) throws Exception {

	ArrayList<Class> availableClasses = new ArrayList<>();
	
	ArrayList<String> classNames = getClassNamesFromJar(filePath);
	File f = new File(filePath);

	URLClassLoader classLoader = new URLClassLoader(new URL[]{f.toURI().toURL()});
	for (String className : classNames) {
		try {
			Class cc = classLoader.loadClass(className);
			availableClasses.add(cc);
		} catch (ClassNotFoundException e) {
			LOGGER.error("Class " + className + " was not found!", e);
		}
	}
	return availableClasses;
}

Since all the classes in the jar file are now loaded, we can invoke any of its functions. For eg., to get all the declared methods in a class, we can simply use

loadedClass.getDeclaredMethods()

But still our solution is not even satisfactory sometimes. Jar files can be really big with so many dependencies. And we might want to load only certain classes or get only certain methods.

If there is a need to do such filtering in the loaded plugin, we must indicate it through some markers that are understandable to both the plugin and the main program that would load the plugin. And this marking can be done in different ways depending upon the needs. It could be done through interfaces, abstract classes etc. And these can be reflected upon through the dynamically loaded class. The other method could by making use of java annotations. Java annotations has their own advantages and could be more suited for some purpose and not so much for some other.

Below is an example of a class annotation and a method annotation. We can add many different required properties as well to the annotations

// Class annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyContainer {
}

// Method annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyFunction {
}

Once we have these annotations, we can perform, say a filtering on classes by simply checking whether the annotation is present or not. Annotations with properties simply opens up a lot many possibilities to interact with a dynamically loaded class or method at runtime.

// this line can be added to our loadjarfile method written previously
if (cc.isAnnotationPresent(MyContainer.class)) {
	availableClasses.add(cc);
}

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *