The Artima Developer Community
Sponsored Link

Chapter 8 of Inside the Java Virtual Machine
The Linking Model
by Bill Venners

<<  Page 2 of 20  >>


Resolution and Dynamic Extension

In addition to simply linking types at run-time, Java applications can decide at run-time which types to link. Java's architecture allows Java programs to be dynamically extended, the process of deciding at run-time other types to use, loading them, and using them. You can dynamically extend a Java application by passing the name of a type to load to either the forName() method of class java.lang.Class or the loadClass() method of an instance of a user-defined class loader, which can be created from any subclass of java.lang.ClassLoader. Either of these approaches enable your running application to load types whose names are not mentioned in the source code of your application, but rather, are determined by your application as it runs. An example of dynamic extension is a Java-capable web browser, which loads class files for applets from across a network. When the browser starts, it doesn't know what class files it will be loading across the network. The browser learns the names of the classes and interfaces required by each applet as it encounters the web pages that contain those applets.

The most straightforward way to dynamically extend a Java application is with the forName() method of class java.lang.Class, which has two overloaded forms:

// A method declared in class java.lang.Class:
public static Class forName(String className)
    throws ClassNotFoundException;
public static Class forName(String className, boolean initialize,
    ClassLoader loader)
    throws ClassNotFoundException;
The three parameter form of forName(), which was added in version 1.2, takes the fully qualified name of the type to load in the String className parameter. If the boolean initialize parameter is true, the type will be linked and initialized as well as loaded before the forName() method returns. Otherwise, if the boolean initialize parameter is false, the type will be loaded and possibly linked but not explicitly initialized by the forName() method. Nevertheless, if the type had already been initialized prior to the forName() invocation, the type returned will have been initialized even though you pass false as the second parameter to forName(). In the third parameter, ClassLoader loader, you pass a reference to the user-defined class loader from which you want forName() to request the type. You can also indicate that you want forName() to request the type from the bootstrap class loader by passing null in the ClassLoader loader parameter. The version of forName() that takes one parameter, the fully qualified name of the type to load, always requests the type from the current class loader (the loader that loaded the class making the forName() request) and always initializes the type. Both versions of forName() return a reference to the Class instance that represents the loaded type, or if the type can't be loaded, throws ClassNotFoundException.

The other way to dynamically extend a Java application is to load classes via the loadClass() method of a user-defined class loader. To request a type from a user- defined class loader, you invoke loadClass() on that class loader. Class ClassLoader contains two overloaded methods named loadClass(), which look like this:

// A method declared in class java.lang.ClassLoader:
protected Class loadClass(String name)
    throws ClassNotFoundException;
protected Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException;

Both loadClass() methods accept the fully qualified name to load in their String name parameter. The semantics of loadClass() are similar to those of forName(). If the loadClass() method has already loaded a type with the fully qualified name passed in the String name parameter, it should return the Class instance representing that already loaded type. Otherwise, it should attempt to load the requested type in some custom way decided upon by the author of the user-defined class loader. If the class loader is successful loading the type in its custom way, loadClass() should return the Class instance representing the newly loaded type. Otherwise, it should throw ClassNotFoundException. The details on writing your own user-defined class loader are given later in this chapter.

The boolean resolve parameter of the two-parameter version of loadClass() indicates whether or not the type should be linked as well as loaded. As mentioned in previous chapters, the process of linking involves three steps: verification of the loaded type, preparation, which involves allocating memory for the type, and optionally, resolution of symbolic references contained in the type. If resolve is true, the loadClass() method should ensure that the type has been linked as well as loaded before it returns the Class instance for that type. If resolve is false, the loadClass() method will merely attempt to load the requested type and not concern itself with whether or not the type is linked. Because the Java virtual machine specification gives implementations some flexibility in the timing of linking, when you pass false in the resolve parameter, the type you get back from loadClass() may or may not have already been linked. The two parameter version of loadClass() is a legacy method whose resolve parameter has, since Java version 1.1, really served no useful purpose. In general, you should invoke the one-parameter version of loadClass(), which is equivalent to invoking the two-parameter version with resolve set to false. When you invoke the one-parameter version of loadClass(), it will attempt to load and return the type, but will leave the timing of linking and initializing the type to the virtual machine.

Whether you should use forName() or invoke loadClass() on a user-defined class loader instance depends on your needs. If you have no special needs that require a class loader, you should probably use forName(), because forName() is the most straightforward approach to dynamic extension. In addition, if you need the requested type to be initialized as well as loaded (and linked), you'll have to use forName(). When the loadClass() method returns a type, that type may or may not be linked. When you invoke the single parameter version of forName(), or invoke the three-parameter version and pass true in the initialize parameter, the returned type will definitely have been already linked and initialized.

Initialization is the reason, for example, that JDBC drivers are usually loaded with a call to forName(). Because the static initializers of each JDBC driver class registers the driver with a DriverManager, thereby making the driver available to the application, the driver class must be initialized, not just loaded. Were a driver class loaded but not initialized, the static initializers of the class would not be executed, the driver would not become registered with the DriverManager, and the driver would therefore not be available to the application. Loading a driver with forName() ensures that the class will be initialized, which ensures the driver will be available for use by the application after forName() returns.

Class loaders, on the other hand, can help you meet needs that forName() can't. If you have some custom way of loading types, such as by downloading them across a network, retrieving them from a database, extracting them from encrypted files, or even generating them on the fly, you'll need a class loader. One of the primary reasons to create a user-defined class loader is to customize the way in which a fully qualified type name is transformed into an array of bytes in the Java class file format that define the named type. Other reasons you may want to use a class loader rather than forName() involve security. As mentioned in Chapter 3, "Security," the separate namespaces awarded to each class loader enable you to in effect place a shield between the types loaded into different namespaces. You can write a Java application such that types cannot see any types that aren't loaded into the same namespace. Also, as mentioned in Chapter 3, class loaders are responsible for placing loaded code into protection domains. Thus, if your security needs include a custom way to place loaded types into protection domains, you'll need to use class loaders rather than forName().

Both the general process of dynamic extension and the separate namespaces awarded to individual class loaders are supported by one aspect of resolution: the way a virtual machine chooses a class loader when it resolves a symbolic reference to a type. When the resolution of a constant pool entry requires loading a type, the virtual machine uses the same class loader that loaded the referencing type to load the referenced type. For example, imagine a Cat class refers via a symbolic reference in its constant pool to a type named Mouse. Assume Cat was loaded by a user-defined class loader. When the virtual machine resolves the reference to Mouse, it checks to see if Mouse has been loaded into the namespace to which Cat belongs. (It checks to see if the class loader that loaded Cat has previously loaded a type named Mouse.) If not, the virtual machine requests Mouse from the same class loader that loaded Cat. This is true even if a class named Mouse had previously been loaded into a different namespace. When a symbolic reference from a type loaded by the bootstrap class loader is resolved, the Java virtual machine uses the bootstrap class loader to load the referenced type. When a symbolic reference from a type loaded by a user- defined class loader is resolved, the Java virtual machine uses the same user-defined class loader to load the referenced type.

<<  Page 2 of 20  >>

Sponsored Links

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use