Anne, I had the recent opportunity to attend a presentation on Class Loaders at the Mountain View Java Users Group. Jean Tillinghast may have already forwarded you a copy that had some inaccessible HP-internal links and one copyrighted class definition, but here is a "clean" copy. These are just the notes as best that I was able to catch them and remember them a week later. Class Loader concepts are actually quite simple, as the examples here and on the speaker's Web page indicate. There were two key concepts: 1) the concept of classes existing at run-time as instances of the class Class (familiar to Smalltalk programmers, but not C++ programmers), and 2) the name-space/class-loader relationship and the ability to have multiple name spaces for classes (obvious once you think about loading applets, etc.). I was very pleased with the meat in this presentation. I would not hesitate to attend any other presentation that Bill Venners were giving. It was a 5.5-hour round-trip from Santa Rosa that was well worth it. All of the material here came from my notes, but he has excellent detailed examples under the "Writing Class Loaders" heading and link. His material is copyrighted. I think it is pretty self-explanatory, but, then, I was there. If there are any questions, please feel free to write or call. You may post this to the class if you think it would be helpful, even though it may be slightly outside the scope of the syllabus. Wayne Cannon 707-577-4788 ################################################################## Notes from the 25 Feb 98 Mountain View Java Users' Group (MTVJUG): ------------------------------------------------------------------ The speaker is Bill Venners of the Artima Software Company talking about Java ""class loaders". [These notes are necessarily terse, but you will get an idea of what was discussed and basically how to do some of the operations.] Administrative preamble stuff: March: no meeting. April: Understanding Java 1.2, Bruce Eckel May: Beans Tutorial, Mark Johnson June: No speaker. Bring your favorite Java program, bean, etc. ----------------------------- Bill Venners, Artima Software Company, consultant, trainer, and author of the book, Inside the Java Virtual Machine (http://www.artima.com/insidejvm/blurb.html). His Artima Web site http://www.artima.com/ has quite a number of excellent examples and tutorial pieces. It is well worth visiting. It includes his PowerPoint slides and example code demonstrating how to write a "class loader". A "zip" file is attached, or you can access them for a while from http://web.sr.hp.com/~waynec/ClassLoaders.zip. Bill asked how many have written a "class loader". About 8 people (10-15% of the group) responded. "Class loaders" allow dynamic extension, loading at run-time, types to load and use. The type (class) names are given as strings. There are two ways: 1) use the forName() function, and 2) use a custom "class loader". For example: Loading an applet under a Web browser. 1) Load a class using the "primordial" (or System) "class loader" that is part of the JVM. 2) Use "class loader" objects, or custom ""class loaders", that are part of an application Why have a custom "class loader"? - To load class definitions from a place other than Java's normal CLASSPATH, such as from a custom directory, from a database, over the network, etc. - To perform some custom name modification, such as appending or prepending something to the name provided, or assuring that the name meets some custom criteria before looking for the class definition. - To provide custom exception handling, including looking in alternate locations if the class definition isn't found immediately. - To provide an additional "name space" layer for security purposes -- see below. - Etc., etc., etc. Important: Each loader has its own "name space". The name space used at load time and at run time is "name space" of the "class loader" that loaded the object. All class names are unique within a "name space". Class names do not have to be unique across "name spaces". For example [using an outline to describe a class hierarchy where an animal is an object, and both cat and mouse are animals]: java.lang.Object Animal Cat Mouse java.lang.Object Device Keyboard Mouse Note that the two "Mouse" classes are different classes--one is a rodent and the other is a device attached to your computer. Both can exist in the same program at the same time, but in different "name spaces" -- i.e., if they are loaded by different ""class loaders". The "class loader" and the "name space" have a one-to-one (1:1) relationship. The same loader's "name space" is used as for the referencING object. "Name spaces" are important for: 1) Dealing with name conflicts (especially when loading code written by a different developer, such as when a program (such as the initial Java program launched by a Web browser) loads your applet. Your applet does not have to avoid the class names used in the initial program. 2) Shielding untrusted code. By running untrusted code in a different "name space", it is prohibited from overriding the fundamental library classes and allowing it illegal access. Sunsoft's JVM book has a very good reference on ""class loaders". We will create a new NetworkClassLoader class based on the system's java.lang.ClassLoader class, and use an instance of our new NetworkClassLoader to load a new class into the system. java.lang.Object java.lang.ClassLoader NetworkClassLoader Device Keyboard Mouse Animal Cat Rodent Mouse For the purpose of this example, we will assume that the classes Device, Keyboard, Mouse, Animal, Cat, and Rodent are loaded normally, i.e., by the primordial "class loader", so their class names are in its "name space", that is, the program's main "name space". Now an instance of Cat wants to load a new, and unknown type of Rodent to play with. It will read the name of the new Rodent subclass/type from somewhere into a String. An important concept that is the same as Smalltalk but different from C++ is that Java has run-time class information. Every class definition exists in the system at run-time as an instance of the class "Class". main() { NetworkClassLoader ncl = new NetworkClassLoader(); // ncl is now a net "class loader", and therefore we have created // a new "name space". Class c = ncl.loadClass(args[0], true); // args[0] is, unlike C & C++, the first argument on the command line, // which is, in this case, the name of the new class -- "Mouse" -- // or any other new class. // loadClass() has now created a new instance, c, of class "Class" // (java.lang.Class) by loading the definition of the Mouse // class into the system. Instance "c" is the Mouse class // definition, but we simply don't know its name here, at // compile time. // The second argument, "true", tells loadClass() that the new class // is to be fully "resolved" (the final phase of the initialization // process. More on this "resolveIt" parameter later. // java.lang.Class allows you to getInfo(), create a newInstance(), and // use reflection. Object o = c.newInstance(); // o is a generic Object-class reference to a Mouse-class instance, "c". // However, here at compile time, we don't know what class this // instance will be, so newInstance() returns an Object-class // reference. Rodent myToy = (Rodent)o; // Cast the generic Object-class instance, "o", into something that we // know something about, i.e., Rodent-class. Remember, here at // compile-time, we know nothing about Mouse, only Rodent. In fact, // the next time the program is run, the parameter passed on the // command line might be "Mole" or "Squirrel", and therefore those // will be the classes loaded and myToy will be an instance of // those classes. myToy.makeNoise(); // Methods from the Rodent-class' interface. myToy.run(); // that we know about at compile time. // etc. } --------------------------- Now, about ""class loaders" and "name spaces": "ncl" loads the class "Rodent" by referencing it (i.e., in the "implements" definition of "Mouse" class). It doesn't exist in its own "name space", so it asks the loader that loaded NetworkClassLoader (the primordial loader, in this case). The primordial "class loader" knows about Rodent and loads it, and it returns the Rodent definition back to the NetworkClassLoader. The Rodent class winds up in BOTH "name spaces" (that of the primordial loader, and that of "ncl"). --------------------------- Now a more detailed example, called Greeter, of the actual "class loader" code, including the implementation of the loadClass() method: Greeter is a kind of fancy "Hello World!" that has multiple greetings based on generating instances of dynamically loaded classes (each with its own greeting style) that are selectively loaded based on a command-line parameter. It consists of the Greeter interface, which is known at compile time, and an extensible set of dynamically loaded subclasses that are specified by the command line at run-time. public interface Greeter { void greet(); } For example, here are a couple of the classes that will be selected and dynamically loaded: public class Hello implements Greeter { public void greet() { System.out.println("Hello, world!"); } } public class Greetings implements Greeter { public void greet() { System.out.println("Greetings, planet!"); } } Plus, there are classes that randomly select between messages or that give you an appropriate message for the time of day. public class GreeterClassLoader extends ClassLoader { public synchronized Class loadClass(String className, boolean resolveIt) throws ClassNotFoundException { Class result; byte classData[]; // etc. ... } } Every "class loader" must do the following seven (7) things: 1) Whenever you load the same class as before, you must return the SAME class instance. I do this by attempting to look up the class. If I am successful, I return the class I found. result = findLoadedClass(className); if (result != null) return result; 2) Give the primordial loader a chance. try { result = findSystemClass(className); return result; } catch (ClassNotFoundException) { // "catch clause" just falls through } 3) Disallow insertion of anything into java.* for security. if (className.startsWith("java.") // ... 4) Attempt to load class in your custom way, i.e., get bytes Note that the class is loaded from the input stream into a byte array. 5) Import type (class) and its superclasses (if any) defineClass(...) 6) Initialize any superclasses and the type resolveClass(...) The boolean parameter in the loadClass() method determines whether or not the resolveClass() function gets called. In some cases, while in the process of loading a class, another class needs to be loaded simply to verify its existence and its interface. These classes are "loaded", but not "resolved". They are not resolved until they are actually referenced in the code. 7) Return reference to the new class instance These steps are all quite obvious when you look at his implementation of the GreeterClassLoader class posted on his Web page at http://www.artima.com/talks/index.html.