Custom Class Loaders
The solution to fine-control class loading is to implement custom class loaders. Any custom class loader should have java.lang.ClassLoader as its direct or distant super class. Moreover, in the constructor, we need to set the parent class loader, too. Then, we have to override the findClass() method. The differentversionspush folder contains a custom class loader called FileSystemClassLoader. Its structure is shown in Figure 9:
Figure 9. Custom class loader relationship
Below are the main methods implemented in common.FileSystemClassLoader:
public byte[] findClassBytes(String className){ try{ String pathName = currentRoot + File.separatorChar + className. replace('.', File.separatorChar) + ".class"; FileInputStream inFile = new FileInputStream(pathName); byte[] classBytes = new byte[inFile.available()]; inFile.read(classBytes); return classBytes; } catch (java.io.IOException ioEx){ return null; } } public Class findClass(String name)throws ClassNotFoundException{ byte[] classBytes = findClassBytes(name); if (classBytes==null){ throw new ClassNotFoundException(); } else{ return defineClass(name, classBytes, 0, classBytes.length); } } public Class findClass(String name, byte[] classBytes)throws ClassNotFoundException{ if (classBytes==null){ throw new ClassNotFoundException( "(classBytes==null)"); } else{ return defineClass(name, classBytes, 0, classBytes.length); } } public void execute(String codeName, byte[] code){ Class klass = null; try{ klass = findClass(codeName, code); TaskIntf task = (TaskIntf) klass.newInstance(); task.execute(); } catch(Exception exception){ exception.printStackTrace(); } }
This class is used by the client to convert the client.TaskImpl(v1) to a byte[]. This byte[] is then send to the RMI Server Execution Engine. In the server, the same class is used for defining the class back from the code in the form of byte[]. The client-side code is shown below:
public class Client{ public static void main (String[] args){ try{ byte[] code = getClassDefinition ("client.TaskImpl"); serverIntf.execute("client.TaskImpl", code); } catch(RemoteException remoteException){ remoteException.printStackTrace(); } } private static byte[] getClassDefinition (String codeName){ String userDir = System.getProperties(). getProperty("BytePath"); FileSystemClassLoader fscl1 = null; try{ fscl1 = new FileSystemClassLoader (userDir); } catch(FileNotFoundException fileNotFoundException){ fileNotFoundException.printStackTrace(); } return fscl1.findClassBytes(codeName); }}
Inside of the execution engine, the code received from the client is given to the custom class loader. The custom class loader will define the class back from the byte[], instantiate the class, and execute. The notable point here is that, for each client request, we use separate instances of the FileSystemClassLoader class to define the client-supplied client.TaskImpl. Moreover, the client.TaskImpl is not available in the class path of the server. This means that when we call findClass() on theFileSystemClassLoader, the findClass() method calls defineClass() internally, and the client.TaskImpl class gets defined by that particular instance of the class loader. So when a new instance of the FileSystemClassLoader is used, the class is defined from the byte[] all over again. Thus, for each client invocation, class client.TaskImpl is defined again and again and we are able to execute "different versions" of the client.TaskImpl code inside of the same Execution Engine JVM.
public void execute(String codeName, byte[] code)throws RemoteException{ FileSystemClassLoader fileSystemClassLoader = null; try{ fileSystemClassLoader = new FileSystemClassLoader(); fileSystemClassLoader.execute(codeName, code); } catch(Exception exception){ throw new RemoteException(exception.getMessage()); } }
Examples are in the differentversionspush folder. The server and client side consoles are shown in Figures 10, 11, and 12:
Figure 10. Custom class loader execution engine
Figure 10 shows the custom class loader Execution Engine VM console. We can see the client.TaskImpl code is loaded more than once. In fact, for each client execution context, the class is newly loaded and instantiated.
Figure 11. Custom class loader engine, Client 1
In Figure 11, the code for the TaskImpl class containing the log statement client.TaskImpl.class.getClassLoader(v1) is loaded by the client VM, and pushed to the Execution Engine Server VM. The client VM in Figure 12 loads a different code for the TaskImpl class containing the log statementclient.TaskImpl.class.getClassLoader(v2), and pushes to the Server VM.
Figure 12. Custom class loader engine, Client 2
This code example shows how we can leverage separate instances of class loaders to have side-by-side execution of "different versions" of code in the same VM.
Class Loaders In J2EE
The class loaders in some J2EE servers tend to drop and reload classes at different intervals. This will occur in some implementations and may not on others. Similarly, a web server may decide to remove a previously loaded servlet instance, perhaps because it is explicitly asked to do so by the server administrator, or because the servlet has been idle for a long time. When a request is first made for a JSP (assuming it hasn't been precompiled), the JSP engine will translate the JSP into its page implementation class, which takes the form of a standard Java servlet. Once the page's implementation servlet has been created, it will be compiled into a class file by the JSP engine and will be ready for use. Each time a container receives a request, it first checks to see if the JSP file has changed since it was last translated. If it has, it's retranslated so that the response is always generated by the most up-to-date implementation of the JSP file. Enterprise application deployment units in the form of .ear, .war, .rar, etc. will also needs to be loaded and reloaded at will or as per configured policies. For all of these scenarios, loading, unloading and reloading is possible only if we have control over the application server's JVM's class-loading policy. This is attained by an extended class loader, which can execute the code defined in its boundary. Brett Peterson has given an explanation of class loading schemas in a J2EE application server context in his article "" at .
Summary
The article talked about how classes loaded into a Java virtual machine are uniquely identified and what limitations exist when we try to load different byte codes for classes with the same names and packages. Since there is no explicit class versioning mechanism, if we want to load classes at our own will, we have to use custom class loaders with extended capabilities. Many J2EE application servers have a "hot deployment" capability, where we can reload an application with a new version of class definition, without bringing the server VM down. Such application servers make use of custom class loaders. Even if we don't use an application server, we can create and use custom class loaders to finely control class loading mechanisms in our Java applications. Ted Neward's book throws light onto the ins and outs of Java class loading, and it teaches those concepts of Java that underlie the J2EE APIs and the best ways to use them.
References
- for this article
- The
- "" in the Java tutorial
- "" from
- "" from
- "" from
- "" from
- by Ted Neward
is a Senior Technical Architect at Communication Service Providers Practice (CSP) of , and is a Sun Microsystems Certified Enterprise Architect and a Microsoft Certified Professional.