We'll now explore the details of how a class loader actually loads classes. There is a single method of the ClassLoader class (and all its subclasses) that accomplishes this:
Load and resolve the named class. A ClassNotFoundException is thrown if the class cannot be found.
This is the simplest way to use a class loader directly: it simply requires that the class loader be instantiated and then be used via the loadClass() method. Once the Class object has been constructed, there are three ways in which a method in the class can be executed:
A static method of the class can be executed using the native method interface of the Java virtual machine. This is the technique the Java virtual machine uses to execute the main() method of a Java application once the initial class has been loaded, but this is not generally a technique used by Java applications.
An object of the class can be constructed using the newInstance() method of the Class class, but only if the class has an accessible constructor that requires no arguments. Once the object has been constructed, methods with well-known signatures can be executed on the object. This is the technique that a program like appletviewer uses: it loads the initial class of the applet, constructs an instance of the applet (which calls the applet's no-argument constructor), and then calls the applet's init() method (among other methods).
Starting with JDK 1.1, the reflection API can be used to call a static method on the class, or to construct instances of the object and execute methods on that object. The reflection API allows more flexibility than the second choice, since it allows arguments to be passed to the constructor of the object. This is the technique that is used by our JavaRunner program.
The second case is more commonly implemented, if only because it's simpler (and it is applicable in all versions of Java). But consider the following modifications to our JavaRunner program:
public class JavaRunner implements Runnable { final static int numArgs = 2; ClassLoader cl; String className; Object args[]; JavaRunner(ClassLoader cl, String className, Object args[]) { this.cl = cl; this.className = className; this.args = args; } void invokeMain(Class clazz) { .. unchanged .. } public void run() { Class target = null; try { target = cl.loadClass(className); invokeMain(target); } catch (ClassNotFoundException cnfe) { System.out.println("Can't load " + className); } } static Object[] getArgs(String args[]) { .. unchanged .. } public static void main(String args[]) throws ClassNotFoundException { Class self = Class.forName("JavaRunner"); JavaRunnerLoader jrl = new JavaRunnerLoader(args[0], self.getClassLoader()); ThreadGroup tg = new ThreadGroup("JavaRunner Threadgroup"); Thread t = new Thread(tg, new JavaRunner(jrl, args[1], getArgs(args))); t.start(); try { t.join(); } catch (InterruptedException ie) { System.out.println("Thread was interrupted"); } } }
We've replaced the forName() method that we used in our example in Chapter 1, "Java Application Security" with the highlighted code here: now we construct a class loader (an instance of the JavaRunnerLoader class, the definition of which we'll see in just a bit) and are now using the loadClass() method to find our target class.
In Java 1.2, constructing the class loader requires that we find the class loader that loaded our class and pass that to the constructor of the JavaRunnerLoader class. In 1.1, we would not use the self instance variable.
We've also changed the arguments required to run this program, which is why we've changed the definition of numArgs. Previously, we required the name of the class and any arguments the class requires. Now we require an additional argument: the name of the URL from which to load all the classes. Hence, if our Cat class was on the web server named piccolo, we could run our JavaRunner example like this:
piccolo% java JavaRunner http://piccolo/ Cat /etc/passwd root:x:0:1:0000-Admin(0000):/:/usr/bin/csh daemon:x:1:1:0000-Admin(0000):/: bin:x:2:2:0000-Admin(0000):/usr/bin: ...
Note the difference between this implementation and the one we showed in Chapter 1, "Java Application Security". In this case, the Cat class is loaded from the JavaRunner class loader, and any classes the Cat class needs are dynamically loaded from that class loader. In Chapter 1, "Java Application Security", what happened was a product of the release of Java. In 1.1, the Cat class was loaded from the primordial class loader; any classes it required were loaded from the primordial class loader as well. In 1.2, the Cat class was loaded from an instance of the URLClassLoader class, and any classes it required were loaded from that class loader as well.
The practical result is that the security manager and access controller will give different permissions to the Cat class depending on which class loader loaded it: the permissions that are assigned to a class may be different depending upon whether the class was loaded from the URL class loader, the JavaRunner class loader, or the primordial class loader. Exactly how those permissions differ depends upon the internal implementation of the class loader as well as the security manager and access controller that are in effect. In a nutshell, these differences will be based upon where the class loader found the class, and whether or not that class was signed.
Copyright © 2001 O'Reilly & Associates. All rights reserved.