HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

One of the primary reasons for the success of Java has been its robust dynamic class loading mechanism. The Java Virtual Machine ClassLoader is a mechanism that Java uses to load classes at runtime. Techniques to take advantage of Java's dynamic nature and the interactive nature of the Web usually create a spark of interest in the Java community.

This article leads the reader through the development of a new Java ClassLoader mechanism that leverages the Web for downloading Java classes. It begins with an overview of the Java ClassLoader mechanism, followed by an examination of how Java's ClassLoader can be extended to download Java classes from multiple Web servers to be used by a single Java application. This will allow users to develop applications that download behavior dynamically and extend their functionality on the fly using the Web.

The Mechanics of the Java ClassLoader
The Java Virtual Machine ClassLoader is a mechanism used to load classes from files in directories or zip files specified in the java.class.path system property. This property is set using the CLASSPATH environment variable. Classes are searched sequentially through the directories and zip files contained in the java.class.path property. The default setting for the java.class.path property is the subdirectory where the JDK has been installed and the zip file containing the Java classes (i.e., classes.zip). The ClassLoader is responsible for taking a class name and loading it into the Java runtime process.

Another important activity of the ClassLoader is to link a Class. Linking takes a binary class and incorporates it into the Java Virtual Machine Runtime. This allows a class to be executed. There are three distinct operations involved when linking:

  1. Verification
  2. Preparation
  3. Resolution of symbolic references
Verification ensures that the binary representation of a class maps to a valid operation, the instructions are type safe and the integrity of the Java Virtual Machine is not violated. Preparation initializes the static fields (both variables and constants) of a class with default values, and checks for abstract method consistency between classes. The resolution of symbolic references is used to verify the correctness of a reference (method signature verification, field signature verification, etc.) and substitute symbols with direct reference.

The default ClassLoader can be extended to define specific properties for loading Java classes. Some of the properties that can be defined are:

  • File Format (.class, .zip, .jar, .personal)
  • Class Source (directories)
  • Protocol (http, ftp, file)
  • Security Conditions
Developers can create their own ClassLoaders by extending the ClassLoader abstract class. The ClassLoader abstract class contains the following methods. For a detailed explanation of the ClassLoader abstract class refer to "The Java Class Libraries An Annotated Reference" (Chan and Lee, Addison Wesley, 1997).
  • ClassLoader() - This constructor must be instantiated by the calling application. The main purpose of this constructor, besides protocol-specific initialization, is to trigger a security check against the checkCreateClassLoader method in the acting SecurityManager instance. If a security violation exists, a SecurityException is thrown.

  • loadClass() - This method is the only one whose implementation needs to be provided by a non-abstract ClassLoader. loadClass method is responsible for mapping a class name to a class Class instance. The class loading policies are implemented inside of this method. The normal flow of execution of the loadClass method is to retrieve the class information in a byte array form, call the defineClass method to generate a Class object from the byte arrays, and to resolve the class references if the resolve variable is true. If the class name cannot be resolved to a class Class instance, a ClassNotFoundException is thrown.

  • defineClass() - This method is used by the ClassLoader to generate a Class object from a byte array containing the byte codes for a specific class. Also, the defineClass method is responsible for introducing the generated class into the JVM runtime process. The byte array must contain the format of a valid class. If the byte array doesn't contain a valid class definition, a ClassFormatError is thrown.

  • findSystemClass() - This method loads a class using the default system ClassLoader. This method uses the java.class.path system property to find the class that matches the requested class name. If the class name cannot be resolved to a class Class instance, a ClassNotFoundException is thrown.

  • resolveClass() - This method is used by the ClassLoader to incorporate a class into the JVM runtime process. Another responsibility of the resolveClass method is to map all of the class references contained inside of the class being loaded (including super class, methods and fields).
The next section provides an example of a new ClassLoader, called the URLClassLoader, that extends the ClassLoader abstract class and leverages the Internet and Web browser to download Java classes into Java applications.

URLClassLoader
The URLClassLoader is a ClassLoader mechanism that leverages Web Servers to download Java classes to client Java applications. Java applets are able to download multiple Java classes from the connecting Web server. The URLClassLoader allows Java applications to similarly download multiple Java classes, but unlike Java applets, from multiple Web servers. Once the classes are downloaded they are incorporated into the running Java application.

Unlike regular ClassLoader mechanisms which are file-based, the URLClassLoader requires a URL string in order to identify the location from where to download the class bytecodes. An example of a URL String is given below:

http://www.motorservice.com/
java_classes/texteditor.class.

This String tells the ClassLoader to download a class call texteditor.class from the machine www.motorservice.com in the directory called java_classes.

The URLClassLoader is responsible for downloading the bytecodes from the URL location, instantiating a Class object from the downloaded bytecodes and caching the downloaded Class object for future use. These operations take place outside of the loadClass() method. The loadClass() method is called whenever an object is instantiated from the cached generated Class object using the newInstance() method on the Class class. We have chosen to download bytecodes and to process all of the class information outside of the loadClass() method because the URL information associated with the class name is not contained in the input parameters of the loadClass method. The caching policy may vary between ClassLoader implementations but the idea is the same, viz., to minimize the amount of time you go the network to access a class.

Applications of the URLClassLoader
The following types of applications can benefit from the capabilities of the URLClassLoader:

  • Applications that need to download functionality dynamically based on user request
  • Applications whose download time is very large and can benefit from sub-components download
  • Applications that act as broker or trigger environments for classes without common interfaces
  • Applications that need to interact with classes that do not share a common interface
The application environment presented in the next example is a trigger environment that starts Java applications. These applications are graphical in nature.

URLClass Loader Trigger Application
The trigger environment implemented in this example takes URL strings of the form

http://www.motorservice.com/
java_classes/texteditor.class

The texteditor.class is a Java application that extends the Frame class. The trigger application downloads this type of application over the Internet and executes them. This trigger environment acts as a runtime shell for Java programs. The advantage of having the trigger environment executing the various Java applications is that it can act as a task manager by suspending or stopping the execution of the spawned programs. The user could extend this program to allow components of one application to communicate and interact with components of a second application.

There are two classes associated with the trigger environment, trigger and URLClassLoader. Figure 1 shows the class diagrams of the two classes. The trigger class is responsible for providing the URLClassLoader with the desired Java application to be downloaded. Also, it is responsible for creating an instance of the downloaded Java application class and for starting its execution by calling the main() method on the class. The URLClassLoader is responsible for downloading the bytecodes associated with the URL string and for linking the downloaded Java application class with the running JVM process.

Figure 1
Figure 1:

Figure 2 shows the collaboration diagrams for executing a Java Application downloaded over the Web.

Figure 2
Figure 2:

The trigger environment provides a simple application that allows a user to enter a URL with a Java application class. In order for the Java application class to be downloadable it must be contained in a Web server directory. Figure 3 shows the trigger environment application screen.

Figure 3
Figure 3:

The method registerJavaClass() creates a URL input stream and downloads the byte codes via this mechanism. This is the first method called whenever the "Execute Java Application" button is pressed. The string typed inside the textfield is passed to this method.

// Create URL Stream
u = new URL(URLlocation);

// Open URL Stream
input = u.openStream();
data = new DataInputStream(input);

The registerJavaClass() method continues by downloading the byte codes from the stream and creating a byte array.

// Download byte codes from URL Stream
byte classBytes[] =
downloadByteCodesFromURL(data);

Also, the registerJavaClass() method creates a Class from the downloaded byte array.

// Create class from byte code
ourClass = createClassFromByteCodes
(classBytes, true);

The method createClassFromByteCodes() creates a class by calling the defineClass() method on the default ClassLoader. In addition, this method is responsible for linking the created class object with the JVM runtime process. Once the class object has been linked it caches the object inside of a Hashtable (see Listing 1).

The loadClass() method gets executed whenever the class is being instantiated. This method checks the local java.class.path system property first to load the class locally. It checks the Hashtable cache if it can not find the class locally. If the class being instantiated has not been loaded it proceeds by trying to download the class from the last accessed Web site location (see Listing 2).

If the class being instantiated is not cached, the URLClassLoader attempts to download the requested class from the Web using the last accessed Web site. Fetching the class from the network is done by reconstructing a URL path to the requested class name (see Listing 3).

After the Java application class has been registered by the ClassLoader and instantiated by the trigger class, we use reflection to invoke the static main method inside the Java application. Since we are downloading Java applications over the Web, we expect the applications to contain a main() method. We search the Java class for a main(String[]) method and once we find it we execute the method (see Listing 4).

For a copy of the complete and working trigger program source code, please refer to Listing 5 and Listing 6. An additional enhancement that can be made to the trigger environment application is to persist the Hashtable object. This would allow the permanent caching of downloaded Java applications. However, the drawback to this approach is that as new releases of the applications are stored on Web servers you won't be able to use the new applications.

Conclusion
In this article, we walked through the design of a ClassLoader mechanism that allows you to use the Web servers as extensions of your hard drive. There are security issues involved in implementing your own ClassLoader that we have not discussed here.

About the Author
Israel Hilerio is a member of the Technical Staff at i2 Technologies in Dallas, TX. He holds a B.S. in Computer Science Engineering from St. Mary's University and an M.S. in Computer Science from Southern Methodist University. Israel has eight years of programming experience, two of them in Java.

	

Listing 1.
 
 // Creates a class from the byte codes 
  c = defineClass(className, array, 0, array.length); 
  if (resolve) { 
     // resolves all of the classes needed by the class 
     // links the class into the JVM process 
     resolveClass(c); 

     // caches the class into a Hashtable 
     classList.put(className, c); 
    } 

Listing 2.
 
try { 
     c = findSystemClass(name); 
     System.out.println("Resolved " + name + " locally"); 
    } 

 // If Class is Not Local, Attempt to Resolved it From Downloaded Classes 
catch (ClassNotFoundException e) { 
       System.out.println("Resolving Class: " + name); 
       classRequested = (Class) classList.get(name); 

       if (classRequested == null) { 
      System.out.println("Resolving " + name + " remotely"); 

      // If a Class has not been resolved previously then 
      // attempt to load it from the last Web site that was 
      // accessed. 
                   loadClassName( name ); 
      c = ourClass; 
   } 
             } 
             return c; 

Listing 3.
 
public void loadClassName(String name) { 
                 String URLlocation; 
      try { 
      URLlocation = new String("http://" + serv + dir + "/" + name + ".class"); 
      registerJavaClass(URLlocation); 
                } 
               catch (Exception e) { 
     e.printStackTrace(); 
                } 
             } 

Listing 4.
 
Object obj = c.newInstance(); 
 

 // set the parameters for the static main method 
 Class array1[] = {Class.forName("[Ljava.lang.String;")}; 

 // gets the static main method 
 m1 = c.getMethod("main", array1); 

 // invoke the static main method 
 m1.invoke(obj, object_array); 
  

Listing 5: urlclassloader.java
 
import java.net.*; 
import java.io.*; 
import java.util.*; 

class URLClassLoader extends ClassLoader { 

Class  ourClass=null; 
Hashtable classList=null; 
int  vector_counter; 
String  directory=null; 
String  server=null; 
String  className=null; 
String  UnresolvedURL=null; 
String  temporaryURL=null; 
boolean  flagClass; 
static int counter1; 

 public URLClassLoader() { 

   super(); 

   classList = new Hashtable(); 
 } 

 public void registerJavaClass(String URLlocation) throws IOException { 

    String  filename=null; 
    int  slash_index=0; 
    InputStream  input=null; 
    URL   u=null; 
    DataInputStream data=null; 

    try { 
  u = new URL(URLlocation); 
  temporaryURL = new String(URLlocation); 

  filename = new String(u.getFile()); 
  slash_index = filename.lastIndexOf("/"); 
  className =  
    new String(filename.substring(slash_index+1, 
                       filename.length() - 6)); 

  directory = 
   new String(filename.substring(0, 
                                 slash_index)); 

  server = new String(u.getHost()); 

  input = u.openStream(); 

  data = new DataInputStream(input); 

  byte classBytes[] = 
    downloadByteCodesFromURL(data); 

  data.close(); 
  input.close(); 
   
  input = null; 
  u = null; 
  System.gc(); 
   
  ourClass = 
    createClassFromByteCodes(classBytes, true); 

    } catch (Exception e) { 

            if (input != null) { 
                  input.close(); 
                  if (data != null) { 
                      data.close(); 
                   } 
            } 
         
            u = null; 
         
     UnresolvedURL = new String(URLlocation); 

     ourClass = null; 

     throw new IOException("Problems Defining " + 
              "Network Class +++++++++++++++>"); 
    } 
 } 

 public byte[] downloadByteCodesFromURL( 
                          DataInputStream in) { 

    ByteArrayOutputStream outStream = 
                    new ByteArrayOutputStream(); 
  
    while (true) { 
  try { 
   outStream.write(in.readByte()); 
  }  
  catch (IOException e) {  
   break;  
  } 
    } 
    return outStream.toByteArray(); 
 } 

 public synchronized Class 
 createClassFromByteCodes(byte[] array, 
                          boolean resolve) { 

    Class c = null; 

           try {  
  c = defineClass(className, array, 
                  0, array.length); 
     if (resolve) { 
           resolveClass(c); 
            classList.put(className, c); 
                } 
    } 
    catch (ClassFormatError e) { 
  e.printStackTrace(); 
    } 

    return (c); 
 } 

 public synchronized Class loadClass(String name, 
                               boolean resolve) 
  throws ClassNotFoundException { 
    Class c = null; 
    Class classRequested; 

    // Attempt to Load the Class Locally First 
    try { 
     c = findSystemClass(name); 
     System.out.println("Resolved " + name +  
                        " locally"); 
    } 
    // If Class is Not Local, Attempt to  
    // Resolve it From Downloaded Classes 
           catch (ClassNotFoundException e) { 
     System.out.println("Resolving Class: " + 
                        name); 

     classRequested =  
             (Class) classList.get(name); 

     if (classRequested == null) { 
     System.out.println("Resolving " + name +  
                        " remotely"); 

     // If a Class has not been resolved 
     // previously then attempt to load it 
     // from the last web site that was accessed. 
     loadClassName( name ); 
     c = ourClass; 
  } 
    } 

    return c; 
 } 

 public void loadClassName(String name) { 
    String   URLlocation; 

    try { 
  URLlocation = new String("http://" + server 
           + directory + "/" + name + ".class"); 
  registerJavaClass(URLlocation); 
    } catch (Exception e) { 
  e.printStackTrace(); 
    } 
 } 

 public Class getTheClass() { 
    return (ourClass); 
 } 
} 

Listing 6: trigger.java
 
import java.awt.*; 
import java.net.*; 
import java.io.*; 
import java.lang.reflect.*; 
import java.awt.event.*; 
import java.util.*; 
import URLClassLoader; 

public class trigger extends Frame implements ActionListener{ 

 URLClassLoader  loader=null; 
 TextField  tf = null; 
 Button   bt = null; 

 public trigger() { 
  super("Trigger Application"); 

  setLayout(new BorderLayout()); 
  tf = new TextField(); 
  bt = new Button("Execute Java Application"); 
  bt.addActionListener(this); 

  add("North", 
    new Label("Enter URL for loading Java application")); 
  add("Center", tf); 
  add("South", bt); 

  loader = new URLClassLoader(); 

  setSize(300, 100); 
 } 

 public void actionPerformed(ActionEvent evt) { 
  String URLLocation = tf.getText(); 

  if (URLLocation != null) { 
     executeApplication(URLLocation); 
  } 
 } 

   public void executeApplication( 
                         String URLlocation) { 

  try { 
     loader.registerJavaClass(URLlocation); 

         Class c = loader.getTheClass(); 

     startJavaApplication(c, "New Application"); 
  } 
  catch (IOException e) { 
    System.out.println("Error loading URL Class"); 
  } 
   } 

   public void startJavaApplication( 
                       Class c, String label) { 

                try { 

    Object obj = c.newInstance(); 

           String[] array = new String[2]; 
           array[0] = new String(label); 
           Object object_array[] = {array}; 

           Method m1; 

          // set the parameters for main method 
           Class array1[] = 
           {Class.forName("[Ljava.lang.String;")}; 

    // get the static main method 
    m1 = c.getMethod("main", array1); 

    // invoke the static main method 
    m1.invoke(obj, object_array); 

                } catch (NoSuchMethodException e) { 
          e.printStackTrace(); 
      } catch (ClassNotFoundException e) { 
         e.printStackTrace(); 
      } catch (InvocationTargetException e) { 
         e.printStackTrace(); 
      } catch (IllegalArgumentException e) { 
     e.printStackTrace(); 
         } catch (IllegalAccessException e) { 
     e.printStackTrace(); 
         } catch (InstantiationException e) { 
     e.printStackTrace(); 
  } catch (NullPointerException e) { 
     e.printStackTrace(); 
  } 
   } 

 public static void main(String args[]) { 
  trigger application = new trigger(); 
  application.show(); 
 } 
}


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.