Develop a modular application - The loading
Now that we've seen how to describe a module in Java, we'll see how to load it dynamically in our application.
In Java, all the classes are loaded using several ClassLoader.In this article, we'll develop a loader for our modules and watch the problems that arrive when working with custom ClassLoaders.
Normally, Java use the system ClassLoader to load all the classes of our application. So it contains all the classes of our application and all the classes our application needs to work. But the problem is that we cannot add our modules jar files into classpath because the application doesn't know the modules jar files names.
Moreover, we cannot theoretically add files to the system ClassLoader. I say theoretically because, we can add files using reflection and call to a private method, but i thing it's not a really good practice.
So we've to create a new ClassLoader to load our modules. We'll do that in two phases :
- Browse the module files to get the classes of the modules and the URLs of the modules Jar files
- Load the modules into our ClassLoader using the URLs of the first phase
We'll do all the loading in a new class ModularLoader. so let's create a create a method that return the list of classes to load :
public class ModuleLoader { private static List<URL> urls = new ArrayList<URL>(); private static List<String> getModuleClasses(){ List<String> classes = new ArrayList<String>(); //Get all the modules of the modules folder File[] files = new File("folder").listFiles(new ModuleFilter()); for(File f : files){ JarFile jarFile = null; try { //Open the Jar File jarFile = new JarFile(f); //We get the manifest Manifest manifest = jarFile.getManifest(); //We get the class name from the manifest attributes classes.add(manifest.getMainAttributes().getValue("Module-Class")); urls.add(f.toURI().toURL()); } catch (IOException e) { e.printStackTrace(); } finally { if(jarFile != null){ try { jarFile.close(); } catch (IOException e) { e.printStackTrace(); } } } } return classes; } private static class ModuleFilter implements FileFilter { @Override public boolean accept(File file) { return file.isFile() && file.getName().toLowerCase().endsWith(".jar"); } } }
Like you see, it's not complicated at all. We search all the module files and then for each jar file, we open it, get the manifest et read the class name of the module. And then, for the second phase, we get the URL to the Jar file.
Of course, this loader is not perfect. We can have modules with no manifest or manifest with no class name and the errors must be correctly treated, but this is not the objective of this post to be perfect.
Now we can do the second phase, adding a method to create the ClassLoader, instantiate the modules and return them :
private static ClassLoader classLoader; public static List<IModule> loadModules(){ List<IModule> modules = new ArrayList<IModule>(); AccessController.doPrivileged(new PrivilegedAction<Object>(){ @Override public Object run() { classLoader = new URLClassLoader( urls.toArray(new URL[urls.size()]), ModuleLoader.class.getClassLoader()); return null; } }); //Load all the modules for(String c : getModuleClasses()){ try { Class<?> moduleClass = Class.forName(c, true, classLoader); if(IModule.class.isAssignableFrom(moduleClass)){ Class<IModule> castedClass = (Class<IModule>) moduleClass; IModule module = castedClass.newInstance(); modules.add(module); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return modules; }
So we start creating a new ClassLoader taking the urls of the Jar files. Then, we use this ClassLoader to load all the module classes and instantiate them. We only verify if the class is of type IModule.
This is all for our ModuleLoader. We can now test our simple modular application. We create a JAR file for the module of the previous post and then we create a very simple application to test that :
List<IModule> modules = ModuleLoader.loadModules(); for(IModule module : modules){ System.out.println("Plug : " + module.getName()); module.plug(); } System.out.println("Lot of other things done by the application. "); for(IModule module : modules){ module.unplug(); }
And here is the output of the application :
Plug : Simple module Hello kernel ! Lot of other things done by the application. Bye kernel !
Like you can see, we just created a modular applications ! The application doesn't know the modules, but the modules can do things in the application.
Of course, to create a real applicatio, we have to develop all the extension points and services, but this is a base to start with.
However, there is some problems with the current implementations :
- We cannot deploy modules without restarting the application, because we must create a new ClassLoader for the modules. This is possible if there is no interation between modules, but that's not often the case. You have also the possibility to isolate all the modules in a specific ClassLoader, but with that second solution, the interations between modules are made harder.
- Using a second ClassLoader may be problematic with libraries loading dynamically the classes like Spring or Hibernate. To make these libraries working with your ClassLoader, you have to look at case by case depending on the library. Often, you achieve specifying the contextClassLoader using the method Thread.currentThread().setContextClassLoader(ClassLoader cl) with your ClassLoader
So here is the end of this four posts about creating a modular application. I hope you find these posts interesting.
Comments
Comments powered by Disqus