Sponsored Link •
|
Advertisement
|
Prior to 1.2, the loadClass()
method of
java.lang.ClassLoader
was abstract. To create your own user-defined class
loader, you subclassed ClassLoader
and implemented
loadClass()
. In 1.2, a concrete implementation of
loadClass()
was included in ClassLoader
. This concrete
loadClass()
supports the parent-delegation model introduced in 1.2, and in general
makes it easier and less error prone to create a user-defined class loader. To create a user-defined class
loader in 1.2, you can subclass ClassLoader<
and, rather than override
loadClass()
, you can override findClass()
-- a method
with a much simpler contract than loadClass()
. This approach to creating a user-
defined class loader will be described later in this chapter.
To give you some historical perspective of how class loaders changed between 1.1 and 1.2, consider this
implementation of GreeterClassLoader
, written for 1.1 and included in the first
edition of this book:
// On CD-ROM in file // linking/ex6/COM/artima/greeter/GreeterClassLoader.java package COM.artima.greeter; import java.io.*; import java.util.Hashtable; public class GreeterClassLoader extends ClassLoader { // basePath gives the path to which this class // loader appends "/.class" to get the // full path name of the class file to load private String basePath; public GreeterClassLoader(String basePath) { this.basePath = basePath; } public synchronized Class loadClass(String className, boolean resolveIt) throws ClassNotFoundException { Class result; byte classData[]; // Check the loaded class cache result = findLoadedClass(className); if (result != null) { // Return a cached class return result; } // Check with the primordial class loader try { result = super.findSystemClass(className); // Return a system class return result; } catch (ClassNotFoundException e) { } // Don't attempt to load a system file except through // the primordial class loader if (className.startsWith("java.")) { throw new ClassNotFoundException(); } // Try to load it from the basePath directory. classData = getTypeFromBasePath(className); if (classData == null) { System.out.println("GCL - Can't load class: " + className); throw new ClassNotFoundException(); } // Parse it result = defineClass(className, classData, 0, classData.length); if (result == null) { System.out.println("GCL - Class format error: " + className); throw new ClassFormatError(); } if (resolveIt) { resolveClass(result); } // Return class from basePath directory return result; } private byte[] getTypeFromBasePath(String typeName) { FileInputStream fis; String fileName = basePath + File.separatorChar + typeName.replace('.', File.separatorChar) + ".class"; try { fis = new FileInputStream(fileName); } catch (FileNotFoundException e) { return null; } BufferedInputStream bis = new BufferedInputStream(fis); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { int c = bis.read(); while (c != -1) { out.write(c); c = bis.read(); } } catch (IOException e) { return null; } return out.toByteArray(); } }
The 1.1 GreeterClassLoader
declares one instance variable,
basePath
. This variable, a String
, is used to store the directory
path (passed to GreetingClassLoader
's constructor) in which the
loadClass()
method should look for the class file of the type it has been requested
to load.
The loadClass()
method begins by checking to see if the requested type has
already been loaded by this class loader. It does this by invoking
findLoadedClass()
, an instance method in ClassLoader
,
passing in the fully qualified name of the requested type as a parameter. If this class loader has already been
marked as an initiating class loader of a type with the requested fully qualified name,
findLoadedClass()
will return the Class
instance
representing the type:
// Check the loaded class cache result = findLoadedClass(className); if (result != null) { // Return a cached class return result; }
As mentioned earlier in this chapter, the virtual machine maintains a list of type names that have already
been requested of each class loader. These lists, which include all the types for which each class loader has
been marked as an initiating loader, represent the sets of unique names that currently populate each class
loader's namespace. When loading classes in Step 1a of the process of resolving
CONSTANT_Class_info
entries (described earlier in this chapter), the virtual
machine always checks its internal list before automatically invoking loadClass()
.
As a result, the virtual machine will never automatically invoke loadClass()
on a
user-defined class loader with the name of a type already loaded by that user-defined class loader.
Nevertheless, the GreeterClassLoader
invokes
findLoadedClass()<<
> to check the requested class against the list of the names of the
types it has already loaded. Why? Because even though the virtual machine will never ask a user-defined
class loader to load the same type twice, the application just might.
As an example, imagine the Greet
application were invoked with this command
line:
java Greet greeters Hello Hello Hello Hello HelloGiven this command line, the
Greet
application would invoke
loadClass()
with the name Hello
five times on the same
GreeterClassLoader
object. The first time, the
GreeterClassLoader
would load the class. The next four times, however, the
GreeterClassLoader
would simply get the Class
instance
for Hello
by calling findLoadedClass()
and return that. It
would only load class Hello
once.
If the loadClass()
method determines that the requested type has not been
loaded into its name space, it next passes the name of the requested type to
findSystemClass()
:
// Check with the primordial class loader try { result = super.findSystemClass(className); // Return a system class return result; } catch (ClassNotFoundException e) { }
When the findSystemClass()
method is invoked in a 1.1 virtual machine, the
primordial class loader attempts to load the type. In 1.2, the system class loader attempts to load the type. If
the load is successful, findSystemClass()
returns the Class
instance representing the type, and loadClass()
returns that same
Class
instance.
If the primordial (in 1.1) or system (in 1.2 ) class loader is unable to load the type,
findSystemClass()
throws ClassNotFoundError
. In this
case, the loadClass()
method next checks to make sure the requested class is not
part of the java
package:
// Don't attempt to load a system file except through // the primordial class loader if (className.startsWith("java.")) { throw new ClassNotFoundException(); }
This check prevents members of the standard java
packages
(java.lang
, java.io
, etc.) from being loaded by anything but
the bootstrap class loader. As mentioned in Chapter 3, "Security," two types that declare themselves to be
part of the same named package are only granted access to each other's package-visible members if they
belong to the same runtime package (if they were loaded by the same class loader). But the notion of a
"runtime package" and its affect on accessibility was first introduced in the second edition of the Java virtual
machine specification. Thus, early versions of class loaders had to explicitly prevent user-defined class
loaders from attempting to load types that declare themselves to be part of the Java API (or any other
"restricted" packages) but that couldn't be loaded by the bootstrap class loader.
If the type name doesn't begin with "java."
, the
loadClass()
method next invokes
getTypeFromBasePath()
, which attempts to import the binary data in the user-
defined class loader's custom way:
// Try to load it from the basePath directory. classData = getTypeFromBasePath(className); if (classData == null) { throw new ClassNotFoundException(); }
The getTypeFromBasePath()
method looks for a file with the type name plus
a ".class
" extension in the base directory passed to the
GreeterClassLoader
's constructor. If the
getTypeFromBasePath()
method is unable to find the file, it returns a
null
result and the loadClass()
method throws
ClassNotFoundException
. Otherwise, loadClass()
invokes defineClass()
, passing the byte
array returned by
getTypeFromBasePath()
:
// Parse it result = defineClass(className, classData, 0, classData.length); if (result == null) { System.out.println("GCL - Class format error: " + className); throw new ClassFormatError(); }
The defineClass()
method completes the loading process: it parses the binary
data into internal data structures and creates a Class
instance. The
defineClass()
method does not link and initialize the type. (As mentioned earlier in
this chapter, the defineClass()
method also makes sure all the type's supertypes
are loaded. It does this by invoking loadClass()
on this user-defined class loader for
each direct superclass and superinterface, and recursively applies the resolution process on all supertypes in
the hierarchy.)
If defineClass()
is successful, the loadClass()
method checks to see if resolve
were set to true
. If so, it
invokes resolveClass()
, passing the Class
instance returned
by defineClass()
. The resolveClass()
method links the
class. , it Finally, loadClass()
returns the newly created Class
instance:
if (resolveIt) { resolveClass(result); } // Return class from basePath directory return result;
Sponsored Links
|