Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java Custom Class Loaders


Jun00: Java Custom Class Loaders

Brian is a client applications engineer for emWare, where he writes Java front-end applications for controlling and managing EMIT-enabled embedded devices. He can be contacted at [email protected].


Ordinarily, Java classes are stored in files or as part of a Java archive (JAR file) and are loaded into your programs by the Java VM (system-class loader). You are responsible for making sure the class or JAR files are located some place that is accessible to the Java VM or to your browser. Java takes care of loading and linking the classes into your program.

Relying on the Java VM to load classes is okay as long as a file system is available to store the classes and Java can access the class files. But what about storing classes some place other than in files? One of the great design features of Java is it doesn't limit you to storing classes in files. You can create your own class loader to read and load classes into the Java VM from any data source. This is a very powerful concept. There are some interesting things that can be done once you start to consider places other than files to store classes. Classes can be stored in common devices, a database, or in a class server, and then loaded dynamically when needed. In this age of mobile computing, using classes from a wide variety of sources should become common.

What led me to investigate how to create a custom class loader was the need to store Java classes on devices too small to support a Java VM but accessible through the EMIT device-networking software from emWare (the company I work for). EMIT is emWare's embedded software for network-enabling small devices. Many of these devices use 8-bit microcontrollers and don't have the resources to run a Java VM. In many cases, it is cost prohibitive to add the resources needed to run Java. EMIT provides network access for this class of embedded microcontrollers.

Even though a small device can't run a Java program, it is capable of storing the program needed for its operation. This program can be read from the device (via EMIT) and loaded into a Java VM running on some larger platform that is connected to the device. The device could then be controlled by running the program that was obtained from the device itself. In this way, the device becomes self describing. The program needed to use (or configure) the device is obtained from the device. Because the device is available on a network, the Java code required to use the device can be transferred to any device on the network that is capable of running Java. Without installing any software, you could control a wide range of devices from anywhere on a network.

How to Do It

Although the idea of creating a custom class loader sounds mysterious, it is surprisingly straightforward. Here, I provide an overview of a custom class loader for Java 1.1. Custom class loaders for Java 1.2 are a little different, and I'll point out those differences in this article. Class loaders written for Java 1.1 are compatible with Java 1.2.

To create a custom class loader, you:

1. Extend the class java.lang.ClassLoader. This extension will need a method capable of reading the raw class data into a byte array.

2. Override the method ClassLoader.loadClass. Within loadClass, read in the raw class data and call the method ClassLoader.defineClass to transform the raw data into a Java class in the VM.

3. To load a class, call the loadClass method of your ClassLoader extension.

4. To create a new instance of the class you loaded, call its newInstance method.

You now have a new instance of a class that was loaded from a source you defined. Overall, the process is simple and elegant (as opposed to COM), but there are some important details to know to make it all work.

  • In general, an applet can't create a class loader. The reason is security. Actually, an applet can instantiate its own class loader if the applet is digitally signed and gets permission. Using digitally signed applets is beyond the scope of this article.
  • The class to be loaded must contain a public constructor that takes no parameters; for example, public MyClass() {}.

  • A custom class loader is responsible for loading all the classes required by any class you specifically load. The required classes are all the parent classes, interfaces, and any classes instantiated by the class you are loading. Loading the required classes is handled differently in Java 1.1 and 1.2. In Java 1.1, the custom class loader is responsible for making the appropriate calls to the parent class loader to first search the class cache, and if that fails, it must ask the system loader to handle loading the class. If these actions fail, then the custom class loader is responsible for handling the class.

  • In Java 1.2, the custom class loader is only responsible for loading the class requested and not all the classes required by the requested class. Methods in the class ClassLoader handle loading any required classes and call the subclass to load the requested class.

  • Your class loader should be instantiated only once in your program. Instantiate and assign the class loader to a field within your application class or some other variable whose life time is the length of your program. The class loader maintains a cache of the classes that are loaded. If the class loader is instantiated whenever you need it, the caching mechanism will not work. Classes needed more than once will be read from their source instead of being created from the class cache. This could really slow things down.

The Example

The example application displays a frame with a single button. Clicking the Load Classes button causes the sample classes to be loaded and executed. Each click of the Load Classes button toggles between using the Java 1.1 and Java 1.2 class loader; see Figure 1. The application writes messages to the console showing which classes are being loaded, which class loader was used (system versus the custom loaders), and the execution of the sample classes created by the custom loaders. Listing One, the application output, shows the console output. It's interesting to watch a class loading sequence. You can see all the classes required by either of the sample classes by watching the loading sequence.

For the sake of this article, the custom class loader simply loads classes from files. This isn't that useful (since the Java system already does it), but using files is an easy way to demonstrate how a class loader works without worrying about how to store and retrieve the raw class data. Once you have a basic class loader, you can customize it to load the raw class data from all sorts of places. In fact, I have used a custom class loader to load classes from a board containing a Hitachi H8 microcontroller enabled with EMIT. I also plan on storing classes into a database.

The custom class loader consists of a number of files, including: CustomClassLoader.java, the Java 1.1 class loader (also compatible with Java 1.2); CustomLoader2.java, the Java 1.2 class loader; DeviceController.java, a sample class to be loaded and executed; SimpleSquare .java, a sample class to be loaded and executed; DeviceControls.java, the interface for using the DeviceController class; and Frame1.java, the driver application. All of these files are available electronically; see "Resource Center," page 5.

Using the Loaded Classes

There is, however, a major hurdle to using a class obtained through a custom class loader: How are instances of the class used by the program that needs it? This problem is a little tricky because the usual approach won't work. You simply can't load the class, cast it to the appropriate class type, and use it.

MyClass newClass = (MyClass) custom- Loader.loadClass ("MyClass") ; // no good

This doesn't work (a ClassNotFoundException is thrown) because of the references in the code to our new class MyClass. To load a class containing this line of code, the Java VM will need to load the class MyClass in order to resolve the class references. Because this class is not available to the Java VM (only a custom loader can get it), this doesn't work.

Even if you put the class file for MyClass somewhere the Java VM can find (which will allow you to successfully build the code) your program will stop at this line of code with a ClassCastException error. Huh? The reason this second effort doesn't work is classes loaded from two separate class loaders are considered different types and are not assignment compatible. This is true if both class loaders read the same byte array from the same file (or other source). You can prove this another way by using the instanceof operator to test the classes separately. For this test to work, you must first comment out the line in the method loadClass within CustomClassLoader.java that calls the system loader to load the class. If this change isn't made, the class loader will actually use the system to load the class instead of performing the load itself, which will defeat the test.

// newClass = findSystemClass(name); // ask the system loader to load the class

See Listing Two. The variable theSame will be false.

So how do you use a class if you can't refer to it in your program without first having the class around? And don't try to cheat by building your program using the class file for MyClass, and then removing it when you're ready to run the program. You will get the program to build, but as soon as the Java VM attempts to load the program and encounters a reference to MyClass, the VM will attempt to load MyClass and fail. Remember, Java is a very dynamic and strongly typed language.

To follow are three solutions to solving the chicken-and-egg problem of using classes created by a custom class loader. The first is to extend an existing Java class. The second solution is to create an interface that is implemented by the class to be handled by the custom loader. The third approach is to use the reflection API to operate the classes handled by the custom class loader.

Extend an Existing Java Class

Listing Three is SimpleSquare.java, a sample class that extends Canvas to draw a red square by setting the color and size of the Canvas and letting the inherited methods do the rest. When the class SimpleSquare is loaded, it is cast to a Canvas and not to a SimpleSquare that wouldn't work. This new Canvas can be used like any other Canvas. It is simply added to a frame so it can be displayed (see Frame1.LoadButton_ActionPerformed within Frame1.java; available electronically). This approach works because SimpleSquare is a Canvas, and Canvas is an existing class within the Java VM. Remember though, within the code that uses SimpleSquare, you are limited to using only those methods of the class Canvas and its ancestors. Methods within SimpleSquare are still free to call any methods within SimpleSquare and not just those inherited from Canvas.

This approach can work well for graphics and interface elements. Because new graphics and interface classes are created by extending existing ones and overriding the appropriate methods, there are no compile-time or run-time problems.

Creating an Interface Wrapper

What do you do when you want to load a class that doesn't inherit from an existing class in Java? For example, I've created a device controller class and want to store that class on the device itself. When the device comes online, I will use my class loader to retrieve this device controller class. My device controller class, however, doesn't inherit from an existing Java class; see DeviceController.java (available electronically).

In this situation, I can use an interface to act as a wrapper for the device control class. This interface exposes only those methods that need to be called from the program using the device control class. The interface methods are implemented within my device control class. The class file for the wrapper interface is provided to the Java VM where the program using the device control class will be running. DeviceControls.java (available electronically) shows the interface and Frame1 .LoadButton_ActionPerformed shows its usage.

This technique is just a variation of using an existing Java class. The difference is I am responsible for creating the interface for my device control class instead of relying on an existing Java class. I also must add my interface to the Java VM by copying the class file to a location that is within the VM's class path. An abstract class could also be used in this situation. The abstract class becomes the existing class in the Java VM from which your loaded classes inherit.

Using Reflection

Using reflection is the most generalized and difficult solution. The idea is this: Get the class name and then, based on the class, call methods in the loaded class using the reflection API. Using reflection, you don't need an interface for the classes you load. You can also call any method in the classes you load (provided they are public and there aren't any other security restrictions), but you will have to write code to examine those classes and call the methods via reflection; see Frame1.useReflection (available electronically). This is a lot of work. Also, it doesn't skirt the problem of having to install a class in the Java VM that will be responsible for loading and using the classes you have stored elsewhere. Reflection should be used only as a last resort if there is a situation that can't be handled with an interface. Using an interface to operate the classes you load is much simpler than using reflection and, for the most part, accomplishes the same thing.

Java 1.2

Creating a class loader for Java 1.2 is a little different than 1.1. Instead of overriding ClassLoader.loadClass, a new method named findClass is overridden. In findClass all you have to do is read and define your class. You are freed from the responsibility of having to call other methods to search for a class within the the class cache and the system before loading the class yourself. In Java 1.2, ClassLoader.loadClass performs the required calls for you and only calls your subclasses findClass method when the requested class wasn't found in the system. Sun calls this new technique "delegation" because the responsibility for loading the class is first delegated to the parent class. This approach should make custom class loaders more reliable because you don't have to worry about searching the system before loading a class yourself. This technique only works with Java 1.2, so if you have a need to remain compatible with Java 1.1, stick with the previously described approach; see CustomLoader2.java (available electronically).

Don't Believe Everything You Read

There are places in the Javadoc documentation concerning the class ClassLoader that are incorrect. This makes figuring out what you need to do confusing. Here are the mistakes I found:

  • Java 1.1 API. The Javadoc page shows subclasses of ClassLoader are responsible for maintaining their own cache for the classes they load. This is not the case. The base class ClassLoader has its own cache (a Hashtable) and adds classes to this cache when you call ClassLoader.defineClass. The cache is searched when you call ClassLoader .findLoadedClass.
  • Java 1.2 API. The Javadoc page shows the method ClassLoader.findClass calling ClassLoader.loadClassData to read the class data. Unfortunately, loadClassData doesn't exist. Subclasses of ClassLoader should just override findClass to load the data and define the class. The comments in the source code for ClassLoader are wrong. You have to look at the source to figure this out.

Conclusion

There are several areas concerning class loaders that I haven't mentioned here. Security, of course, is important. Class loaders are constrained by the Security Manager and if you want to use a custom class from within an applet, the applet must be signed and get permission to create a class loader. Also, loading other types of data needed by Java programs to run (sounds, graphics, and the like) is also the responsibility of a class loader. Data other than classes are collectively known as resources (not to be confused with resources on the Windows or Macintosh platforms) and are handled in much the same way as loading a class. See the documentation of ClassLoader.getResource for more details.

Using custom class loaders creates new possibilities for dynamic applications. Applications don't need to be constrained by the classes that are immediately available to the VM. An application can search for the classes it needs from a variety of sources. I expect to see applications in the future using classes from a variety of sources such as embedded devices, servers, and databases.

DDJ

Listing One

-----------------------------------------------------------
java.lang.ClassNotFoundException: mypackage.SimpleSquare
java.lang.ClassNotFoundException: mypackage.DeviceController
Class java.awt.Canvas loaded by the system loader
Class mypackage.SimpleSquare loaded by CustomClassLoader
Class java.lang.Object loaded by the system loader
Class DeviceControls loaded by the system loader
Class mypackage.DeviceController loaded by CustomClassLoader
Class java.lang.Throwable loaded by the system loader
Class java.awt.Rectangle loaded by the system loader
Class java.awt.Component loaded by the system loader
Class java.awt.Color loaded by the system loader
Class java.lang.System loaded by the system loader
Class java.io.PrintStream loaded by the system loader
Device is now on
Getting device power
Battery Power = 10
Calling onOff() and getBatterPower() using reflection
Device is now on
Getting device power
-----------------------------------------------------------
java.lang.ClassNotFoundException: mypackage.SimpleSquare
java.lang.ClassNotFoundException: mypackage.DeviceController
Loading class mypackage.SimpleSquare
Loading class java.awt.Canvas
Class mypackage.SimpleSquare loaded by CustomLoader2
Loading class mypackage.DeviceController
Loading class java.lang.Object
Loading class DeviceControls
Class mypackage.DeviceController loaded by CustomLoader2
Loading class java.lang.Throwable
Loading class java.awt.Rectangle
Loading class java.awt.Component
Loading class java.awt.Color
Loading class java.lang.System
Loading class java.io.PrintStream
Device is now on
Getting device power
Battery Power = 10
Calling onOff() and getBatterPower() using reflection
Device is now on
Getting device power

Back to Article

Listing Two

/* Test a class loaded by the class loader against the same class loaded by the system */
Class  loadedClass  =  customLoader.loadClass("MyClass");
MyClass             vmClass = new MyClass();  // MyClass loaded by Java 
boolean             theSame = vmClass.isInstance(loadedClass);
System.out.println("Are the classes the same? " + theSame);

Back to Article

Listing Three

package mypackage;
import java.awt.*;
/* A real simple Canvas class to test with our class loader.
SimpleSquare appears as a red square. */
public class SimpleSquare extends java.awt.Canvas
{
    public SimpleSquare()
    {
        this.setBounds( new Rectangle( 10, 30, 50, 50) );
        this.setBackground(Color.red);
    }
}

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.