Problem
I want my previously written java application to be run multiple times by a shell script with calculated parameters. However, target platforms may not contain bash. Thus I decied to write another class which acts like a bash script file and launch my application with generated parameters.My application has static variables and static classes. Before every launch I want the state information be cleared. But since I work in the same JVM, static objects will not be removed. It comes out using static fileds and initializers may not be a good approach. There should be a way to solve this.
The solution is to use a custom classloader. Classloader will load all classes. At the end of the execution, the classloader and all the classes loaded will be garbage collected. At next execution, all classes will be reloaded.
Solution : Custom Class Loader
Class loaders can be considered a container to launch an application. Servlet containers like Tomcat uses a custom classloader to launch servlet applications.Writing a custom classloader is a sensitive concept. Multiple loading of the same with different classes should be avoided. It is possible that multiple threads may use the same classloader, thus it should be thread safe.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ScriptClassLoader extends ClassLoader { | |
Map<String, Class> classMap = new HashMap<String, Class>(); | |
@Override | |
public synchronized Class<?> loadClass(String name) throws ClassNotFoundException { | |
// if class is already loaded, return it immediately | |
if (classMap.containsKey(name)) | |
return classMap.get(name); | |
if (name.startsWith("com.myapp") && !name.equals(ScriptClassLoader.class.getName())) { | |
String fileName = name.replace(".", "/") + ".class"; | |
byte[] buf = new byte[2000000]; | |
int len = 0; | |
try { | |
InputStream is = getClass().getClassLoader().getResourceAsStream(fileName); | |
len = is.read(buf); | |
Class definedClass = defineClass(name, buf, 0, len); | |
classMap.put(name, definedClass); | |
return definedClass; | |
} catch (IOException e) { | |
throw new ClassNotFoundException("Error while loading class", e); | |
} catch (NoClassDefFoundError e) { | |
throw new ClassNotFoundException("Error while loading class", e); | |
} catch (Throwable e) { | |
throw new ClassNotFoundException("Error while loading class", e); | |
} | |
} else // leave loading of systems classes to system classloader | |
return getParent().loadClass(name); | |
} | |
} |
Note the synchronized keyword. Allowing multiple threads to access the method, may result in unexpected behaviour. System classes are delegated to parent class loader. Only project classes are loaded. This can be extended to use other framework classes. However, standard java classess needs to be loaded by the system class loader.
When reference to class loader is lost, the class loader and all the classes it loaded is garbage collected.
Launching Main Class
Now that we have class loader we can use reflection to launch application's main method.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
try { | |
ScriptClassLoader scriptClassLoader = new ScriptClassLoader(); | |
Class clazz = scriptClassLoader.loadClass("com.myapp.Main"); | |
Thread.currentThread().setContextClassLoader(scriptClassLoader); | |
clazz.getMethod("main", String[].class).invoke(null, new Object[]{arguments}); | |
} catch (ClassNotFoundException e) { | |
logger.error("Error at loading omspm Main class", e); | |
} catch (IllegalAccessException e) { | |
logger.error("Error at loading omspm Main class", e); | |
} catch (InvocationTargetException e) { | |
logger.error("Error at loading omspm Main class", e); | |
} catch (NoSuchMethodException e) { | |
logger.error("Error at loading omspm Main class", e); | |
} |
Comments
Post a Comment