Understanding Class.forName （一）

Understanding Class.forName<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Abstract

Dynamic loading of Java classes at runtime provides tremendous flexibility in the development of enterprise systems. It provides for the basis of “application servers”, and allows even simpler, lighter-weight systems to accomplish some of the same ends. WithinJava, dynamic loading is typically achieved by calling the forName method on the class java.lang.Class; however, when Class.forName is naïvely called from within an Extension,strange errors can occur. This paper describes why those errors occur, and how Java2 provides two facilities: one a modification to the forName syntax, and the other, called the “Thread context ClassLoader”, to avoid them. This paper assumes you are passingly familiar with Java Reflection, and have at least a general idea of what Java ClassLoaders are and how they are used within Java code.

?

Dynamic runtime loading. One of Java’s strongest features is its ability to dynamically load code given the name of the class to load, without having to know the actual classname until runtime. This allows Java developers to build flexible, dynamic systems that can grow and change without requiring complete recompilation. For example, the following code executes the main method of a class given on the command-line to the class DynamicLoader:

{

public static void main(String[] args)

throws Exception

{

Class <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />toRun = Class.forName(args[0]);

String[] newArgs = scrubArgs(args);

Method mainMethod = findMain(toRun);

mainMethod.invoke(null, new Object[] { newArgs });

}

private static String[] scrubArgs(String[] args)

{

String[] toReturn = new String[args.length-1];

for (int i=1; i

{

toReturn[i-1] = args[i].toLowerCase();

}

}

private static Method findMain(Class clazz)

throws Exception

{

Method[] methods = clazz.getMethods();

for (int i=0; i

{

if (methods[i].getName().equals("main"))

return methods[i];

}

return null;

}

}

As you can see, DynamicLoader takes the arguments (except the first one) given to it, and passes them directly on to the main method of the loaded class, after first making all the command-line arguments lower case. This means we can run any compiled Java class through DynamicLoader without having to change DynamicLoader’s code in the slightest. In fact, we can run any executable Java code, from Swing-based client apps to consolebased Web servers—DynamicLoader really doesn’t care. For example, when we run the following class:

public class Echo

{

public static void main (String args[])

{

for (int i=0; i

{

System.out.println("Echo arg"+i+" = "+args[i]);

}

}

}

> java DynamicLoader Echo ONE TWO THREE

we get the following output:

Echo arg0 = one

Echo arg1 = two

Echo arg2 = three

> java DynamicLoader Echo ONE TWO THREE

Exception in thread "main" java.lang.ClassNotFoundException: Echo

at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:191) at java.lang.ClassLoader.loadClass(ClassLoader.java:280) at java.lang.ClassLoader.loadClass(ClassLoader.java:237) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:124) at DynamicLoader.main(DynamicLoader.java:8) 1 Either /jre/lib/ext or the /lib/ext directories, depending on whether the executing version of java is running out of the /bin directory or /bin. 2 This assumes you’ve jar’ed DynamicLoader.class into a .jar and placed the .jar in the /jre/lib/ext directory; if you haven’t, do that now. We can verify that the class, Echo, does in fact exist, either by running Echo directly, or by running the javap utility to list its attributes: > javap Echo Compiled from Echo.java public class Echo extends java.lang.Object { public Echo(); public static void main(java.lang.String[]); } So what gives? Why is DynamicLoader failing to load Echo? What changed between DynamicLoader-on-the-CLASSPATH and DynamicLoader-as-an-Extension? Problem Analysis In order to understand precisely what’s going on here, you have to understand some of the basics of the Java ClassLoader model and what changed in Java2 from JDK 1.1. Java2 ClassLoader Delegation Model. As described in [2], the Java2 ClassLoader model is a “delegating parent” model, meaning that when a ClassLoader is asked to load a class, it first delegates the opportunity to load the requested class to its parent ClassLoader. Only if the parent (a) hasn’t already loaded the Class, and (b) can’t load the Class does the child ClassLoader get the opportunity to provide the Class requested, if it can. This means that ClassLoaders form a hierarchical tree, with the “bootstrap” ClassLoader (the only ClassLoader in the JVM with a “value” of null) as the root of the tree. (More on the bootstrap ClassLoader later.) Any ClassLoader constructed within user code3 must have a parent ClassLoader; if none is provided in the ClassLoader constructor call, the ClassLoader constructors assume this ClassLoader’s parent to be the “system” or ClassLoader. (More on the “system ClassLoader” later.) Implicitly associated with each loaded Class in a JVM is the ClassLoader that loaded it. It is this ClassLoader that is returned from the Class method getClassLoader. Each Class is associated with one-and-only-one ClassLoader, and this is not mutable—Class objects can’t change the ClassLoader reference to point to another ClassLoader. A ClassLoader’s loadClass method is called (either directly by the programmer or implicitly by the JVM) when a client wishes to load a class through that ClassLoader. Under the JDK 1.0 and 1.1 models, the ClassLoader directly overrode loadClass to undertake the necessary actions to load the code. Under JDK 1.2, however, this is no longer the case; instead, the loadClass method calls its ClassLoader parent/delegate’s loadClass to see if the parent recognizes the class. Only if the parent (and, by extensions, all of its parental ancestors, as well) fails to load the class does this ClassLoader get the chance to load the code. To do that, it calls the findClass method, which is the method customized ClassLoaders are now supposed to override. 3 That is, non-JVM code. ? As an example of how this works, consider a JVM embedded inside of a Web browser. When a Web page comes in with an tag on it, the Web browser constructs an instance of its (vendor-specific) AppletClassLoader. This ClassLoader is responsible for obtaining the compiled bytecode from the Website that served the page in the first place; the Web browser simply does something similar to the following: // inside the Web browser somewhere... AppletClassLoader cl = new AppletClassLoader(appletCodebase); Applet applet = (Applet)cl.loadClass(appletClassName).newInstance(); where appletClassName is given within the HTML tag. AppletClassLoader, in its loadClass method, first checks with its parent, which under most JVMs will be the primordial ClassLoader. Let’s track how this applet’s Class gets loaded. Because the system ClassLoader is the parent to the AppletClassLoader, the system ClassLoader is given the opportunity to find the Class. Because the Class code can’t be found along the CLASSPATH, in any of the Extensions directories, or in the core library classes (it’s an applet, remember the code resides up on the server), the system ClassLoader signals failure. This means it’s now AppletClassLoader’s turn, and it examines the tag for any information, like CODEBASE attributes, on the whereabouts of the Class file requested. If AppletClassLoader finds a compiled .jar or .class file, it downloads the code as a binary file and loads it into the JVM (via the defineClass method on ClassLoader) from there. The Web browser can now construct an instance of the Applet class, because now it has the requested Class file loaded into its JVM: It can now call the start, stop, and other methods on Applet. This parent-child relationship of Java2 now releases Java ClassLoader developers from having to remember to check the system ClassLoader to see if the class has already been loaded. More importantly, however, it permits a “chain-loading” of classes from a variety of sources. For example, in a hypothetical enterprise system, I might create a JDBCClassLoader, whose parent is a URLClassLoader pointing to an HTTP server inside my corporate firewall. That URLClassLoader in turn is a child of the primordial ClassLoader. This provides me a three-layered, code-loading facility, where rapidly-changing code, such as business rules (sales promotionals and the like) can be stored in the RDBMS, potentially on a user-by-user basis, if necessary. The less-mutable code is stored on my HTTP server, and the system and bootstrap ClassLoaders are responsible for the core Java classes, as usual. This approach allows the more stable code to “override” more mutable behavior; if you desire the opposite, simply reverse the placement of the JDBCClassLoader and the URLCLassLoader. In each case, because the nature of the ClassLoading mechanism is “parent-first”, whichever ClassLoader is highest up on the hierarchy has “classloading priority” over its children. Because each child delegates to its parent, we will load code in top-down order; in the fictitious aforementioned example, the system ClassLoader gets the first chance, followed by the URLClassLoader, followed by the JDBCClassLoader. Understanding how the system ClassLoader is architected, along with this understanding of the “parent-first” nature of delegating ClassLoaders, yields discovery of the problem. ? Java2/JDK1.2 Default ClassLoader architecture. The Java2 system provides for loading code from one of three places by default: the core library, the Extensions directory (or directories, if you modify the java.ext.dirs property to include multiple subdirectories), and from the directories and/or .jar/.zip files found along the java.class.path property, which in turn comes from the CLASSPATH environment variable. Each of these three locations is in turn covered by its own ClassLoader instance: the core classes, by the bootstrap ClassLoader, the Extensions directory/directories by the extension ClassLoader, and the CLASSPATH by the system or application ClassLoader4. Before we delve too deeply into this, let’s run a quick test. Compiling and executing this code: public class EchoClassLoader { public static void main (String args[]) { ClassLoader current = new EchoClassLoader().getClass().getClassLoader(); while (current != null) { System.out.println(current.getClass()); current = current.getParent(); } } } produces this rather interesting result: > java EchoClassLoader class sun.misc.Launcher$AppClassLoader

class sun.misc.Launcher$ExtClassLoader The result highlights something very interesting. I’ve been speaking of the system ClassLoader as a singular entity, but there are two parents to our class’ ClassLoader: one called Launcher$AppClassLoader, and one called Launcher$ExtClassLoader, both of which live in the sun.misc package. The bootstrap ClassLoader is implemented as part of the VM itself, and cannot be instantiated by Java code. It is this ClassLoader that brings the core Java classes into the VM, allowing the rest of the JVM to load itself. (Take a second to think about this—in order to construct a ClassLoader, which extends Object, the JVM must load the Object class. But in order to do this, it requires a ClassLoader with which to load it!) Normally, this means loading code from the “rt.jar” file in the jdk/jre/lib subdirectory, but under the Sun JVM, the boot.class.path property actually controls it5. 4 At one point, during the JDK 1.2 Beta3 release, it was also called the base ClassLoader. 5 Why this could be important is beyond the scope of this paper. ? The extension ClassLoader, the first child of the boostrap ClassLoader, is implemented in pure Java code. As already mentioned, the extension ClassLoader’s primary responsibility is to load code from the JDK’s extension directories. This in turn provides users of Java the ability to simply “drop in” new code extensions (hence the name “Extension directory”), such as JNDI or JSDT, without requiring modification to the user’s CLASSPATH environment variable. The system, or application, ClassLoader is the ClassLoader returned from the static method ClassLoader.getSystemClassLoader. This is the ClassLoader responsible for loading code from the CLASSPATH, and by default will be the parent to any user-created or user-defined ClassLoader in the system. (As an aside, both of these classes are direct descendants of URLClassLoader. ExtClassLoader reads the java.ext.dirs property for a list of directories. It takes each directory in turn, iterates over each file in the directory, builds a URL out of it, and stores that URL in the URLClassLoader-parent portion of itself. ExtClassLoader also overrides the findLibrary method to search the directory given by the property os.arch under each extension directory for the library matching the requested name. This allows extension directories to also silently support native-libraries in the same “drop-in” manner as Extensions themselves. AppClassLoader reads the java.class.path property, and for each File.pathSeparator-separated entry, builds a URL out of it. Once these two classes are created, however, they behave in all respects like a standard URLClassLoader. These two classes, along with URLClassLoader, are described more fully in [5].) So now we have some deeper insight into the architecture of the ClassLoader system in Java2. Let’s take a moment and see, step-by-step, why Class.forName, when called on a class found within the CLASSPATH, fails when called from within an Extension. To summarize the “normal” ClassLoader tree: Bootstrap ClassLoader Type: none; implemented within the VM Parent: none Loads core runtime library code from sun.boot.class.path Extension ClassLoader Type: sun.misc.Launcher$ExtClassLoader

Loads code found in extension directories (given by java.ext.dirs )

Type: sun.misc.Launcher$AppClassLoader Parent: Extension ClassLoader Returned from ClassLoader.getSystemClassLoader Loads code found on java.class.path (the CLASSPATH variable) ? Calling Class.forName within an Extension. Go back to the original problem: this code: public class DynamicLoader { public static void main(String[] args) throws Exception { Class toRun = Class.forName(args[0]); String[] newArgs = scrubArgs(args); Method mainMethod = findMain(toRun); mainMethod.invoke(null, new Object[] { newArgs }); } // . . . } when called from within an Extension, fails with: > java DynamicLoader Echo ONE TWO THREE Exception in thread "main" java.lang.ClassNotFoundException: Echo at java.net.URLClassLoader$1.run(URLClassLoader.java:202)

at java.security.AccessController.doPrivileged(Native Method)

at java.lang.Class.forName0(Native Method)

at java.lang.Class.forName(Class.java:124)

Walking through the Class.forName call highlights the problem. Class.forName obtains the caller’s ClassLoader, and uses that ClassLoader instance to load the Class into the VM. Written in pseudoc ode, Class.forName looks like:

throws . . .

{

}

It’s fairly easy, now, to spot what the problem is. When a Class is loaded from the CLASSPATH, its associated ClassLoader is the AppClassLoader instance. When that Class invokes Class.forName, it can find other classes along the CLASSPATH or in the Extensions directory because AppClassLoader first delegates to its parent, the ExtClassLoader. Only if the requested class is not found as an Extension is the CLASSPATH searched.

?

When the calling Class is loaded as an Extension, however, its associated ClassLoader is the ExtClassLoader. Thus, when the Extension-loaded class calls Class.forName,

2. The ExtClassLoader, being a good child, gives its parent, the bootstrap ClassLoader, the opportunity to load the class.

3. Because the bootstrap ClassLoader fails to find it in jdk/jre/lib/rt.jar, it returns without a loaded Class.

4. ExtClassLoader’s loadClass method then attempts to load the Class from within its list of URLs (given, as described earlier, in the java.ext.dirs property). Because the class is not within the list of URLs associated with ExtClassLoader, the load fails, and a ClassNotFoundException is thrown.

Put simply, because Extension-loaded classes don’t have the AppClassLoader instance anywhere on their parent-ClassLoader chain, they can’t load any classes off of the CLASSPATH.

Solution

Understanding Class.forName （二）

2004-08-12 12:45:00

CNN入门必读经典：Visualizing and Understanding Convolutional Networks

2017-04-03 12:55:38

Visualizing and Understanding Convolutional Networks(精读)

2014-11-12 10:09:59

2007-03-29 15:09:00

java连接oracle中classforName的作用

2017-02-28 10:32:06

“看懂”卷积神经网(Visualizing and Understanding Convolutional Networks)

2013-12-06 18:15:43

2016-08-20 17:14:47

eclipese连接MySQL出错“Class.forName("com.mysql.jdbc.Driver")”问题解决

2012-08-04 10:38:09

Visualizing and Understanding Convolutional Networks翻译总结

2016-08-31 09:35:52

数据库连接时，Class.forName(driver).newInstance();解析

2014-06-27 22:07:34

不良信息举报

Understanding Class.forName （一）