Sponsored Link •
|
Advertisement
|
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.
Sponsored Links
|