|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 7 of 7
Another way to interact with a dynamically loaded type is to use reflection to find interesting methods in the object, and to invoke these methods. This approach is taken by bean builder tools.
When you drop a bean into a bean builder, the bean builder dynamically extends itself with the classes of the bean. As the bean builder has no idea what kind of bean is coming, it uses reflection to find methods that adhere to JavaBeans style rules, and uses those methods to manipulate the bean.
So, how does an application ever get out of the primordial name space? It does this by creating a class loader object and asking that class loader object to load the type.
To ask a class loader to load a class, simply invoke loadClass() on the class loader object, thereby passing in the fully qualified name of the type to load. The loadClass() method basically performs the same task as forName().
But loadClass() is rebellious -- it doesn't follow the Resolution Rule. Instead of using the same class loader to load the requested type
as it loaded the class containing the code that invoked loadClass(), it uses itself to load the class. (After all, it is a class loader.) Thus, it loads the requested type into its own name space.
Here's an example of a Cat application that uses a class loader object to load a Rodent:
// In file dynaext/ex3/Cat.java
public class Cat {
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
RodentClassLoader rcl = new RodentClassLoader();
Class c = rcl.loadClass(args[0]);
Rodent myToy = (Rodent) c.newInstance();
myToy.scurry();
}
}
// In file dynaext/ex2/Rodent.java
public class Rodent {
public void scurry() {
System.out.println("Rodent scurrying");
}
}
// In file dynaext/ex2/Mouse.java
public class Mouse extends Rodent {
public void scurry() {
System.out.println("Mouse scurrying");
}
}
Cat application first instantiates RodentClassLoader (shown below). The resulting object is a class loader object because class RodentClassLoader extends class java.lang.ClassLoader. Cat invokes loadClass() on this class loader object, passing it the first argument to the application. Assuming the first argument is "Mouse", loadClass() will attempt to load Mouse into its name space. If it is successful, loadClass() will return a reference to the Class instance that represents the newly loaded type. After that, this Cat application looks the same as the previous Cat that used forName(). But the name spaces look different. Figure 5 shows what the name spaces of this Cat application look like after the execution of the scurry() method. The application has two name spaces because it has two class loaders -- the primordial class loader and the class
loader object it instantiated, RodentClassLoader. Although every other class was loaded by the primordial loader and is, therefore, in its name space, class Mouse was loaded by the class loader object, so it sits in the class loader object's name space.

Figure 5. Two name spaces
Read more about Core Java in JavaWorld's Core Java section.
CLASSPATH. The primordial loaders of other JVMs can do it differently. Often, the reason you write a class loader is because you want
to load classes in some other way than the default way provided by the primordial loader. You may, for example, want to download
class files across a network. This is one reason why the class files for applets are loaded through class loader objects.
You may want to decipher encrypted class files as they are read in. You may want to extract binary class definitions out of
some proprietary database. You may want to load class files that end with the extension .purple instead of .class Or, you may even want to generate class definitions on the fly, so that your Java application can continually make itself
smarter. Because you define class loaders in Java code, you are free to do anything to produce a binary stream of data in
the Java class file format given a fully qualified name. (Well, at least you are free to write and compile the class loader.
But in some runtime environments, the SecurityManager may prevent class loaders from being instantiated. Untrusted applets, for example, can't create class loader objects.) Class
RodentClassLoader, shown below, loads classes in a special subdirectory of the current directory named hole. This class loader is a simple example of another potential reason for writing a class loader: to load class files not located
in the class path.
// In file RodentClassLoader.java
import java.io.*;
import java.util.Hashtable;
public class RodentClassLoader extends ClassLoader {
public synchronized Class loadClass(String typeName,
boolean resolveIt) throws ClassNotFoundException {
// See if type as already been loaded by
// this class loader
Class result = findLoadedClass(typeName);
if (result != null) {
// Return an already-loaded class
return result;
}
// Check with the primordial class loader
try {
result = super.findSystemClass(typeName);
// Return a system class
return result;
}
catch (ClassNotFoundException e) {
}
// Don't attempt to load a system file except
// through the primordial class loader
if (typeName.startsWith("java.")) {
throw new ClassNotFoundException();
}
// Try to load it from subdirectory hole.
byte typeData[] = getTypeFromHole(typeName);
if (typeData == null) {
throw new ClassNotFoundException();
}
// Parse it
result = defineClass(typeName, typeData, 0,
typeData.length);
if (result == null) {
throw new ClassFormatError();
}
if (resolveIt) {
resolveClass(result);
}
// Return class from hole
return result;
}
private byte[] getTypeFromHole(String typeName) {
FileInputStream fis;
String fileName = "hole" + 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();
}
}
Greet Application."
java.applet.Applet. The browser dynamically loads and instantiates your applet and casts the resulting reference to type java.applet.Applet. It then invokes init(), start(), and stop() on your applet, methods which are declared in java.applet.Applet. The other way to interact with dynamically loaded types (by discovering interesting methods via reflection) is reasonable
only if you have absolutely no knowledge of what's coming. If you are writing a bean builder, you'll need to do this. Otherwise,
you'll usually write your program such that it expects, as the dynamic extension, some subclass of a known class or some class
that implements a known interface. forName() versus class loader objectsforName() and class loader objects? In general, you should try to use forName() if possible, because forName() is simpler to use and understand. You should use class loader objects if:
Mouse, and later, to throw away that Mouse and dynamically extend the application with another class also named Mouse. You can't do this with forName() because once a type is loaded into a name space, it can't be removed unless the whole name space is removed. With a class
loader, you can throw away a particular class loader object by dropping out all references to it, to any instances of any
classes it loaded, and to any Class instances for the types it loaded. When you drop all these references, the class loader object and all the classes it loaded
become unreferenced and available for garbage collection by the JVM. You can then create a new class loader object with a
fresh (and empty) name space, and load the revised versions of the classes you just threw out with the old name space. This
is how Web servers (running 24 hours a day, 7 days a week) enable servlet programmers to replace a servlet with a new version
of the same servlet.