Mastering the classpath with JWhich

original source from: http://www.flex-compiler.lcs.mit.edu/jdk/tooldocs/solaris/classpath.html

 

At one time or another, developers experience frustration when dealing with the Java classpath. It's not always clear which class the class loader will load, especially when your application's classpath becomes inundated with directories and files. In this article, I will present a tool that can display the absolute pathname of the loaded class file.

Classpath basics
The Java virtual machine (JVM) employs a class loader to load classes used by an application on an as-needed basis. The CLASSPATH environment variable tells the class loader where to find third-party and user-defined classes. You can also specify the classpath on a per-application basis with the -classpath JVM command-line argument, which overrides the classpath specified in the CLASSPATH environment variable.

Classpath entries can be directories that contain class files for classes not in a package, the package root directory for classes in a package, or archive files (such as .zip or .jar files) that contain classes. Classpath entries are colon-separated on Unix-type systems and semicolon-separated on MS Windows systems.

Class loaders are organized in a delegation hierarchy, with each class loader having a parent class loader. When a class loader is asked to find a class, it first delegates the request to its parent class loader before attempting to find the class itself. The system class loader, the default class loader provided by the JDK or JRE installed on your system, loads third-party and user-defined classes using the CLASSPATH environment variable or the -classpath JVM command-line argument. The system class loader delegates to the extension class to load classes that use the Java Extension mechanism. The extension class loader delegates to the bootstrap class loader (the buck stops here!) to load the core JDK classes.

You can develop specialized class loaders to customize how the JVM dynamically loads classes. For example, most servlet engines use a custom class loader to dynamically reload servlet classes that have changed in directories specified in a custom classpath.

Of particular importance, and much consternation, the class loader will load classes in the order they appear in the classpath. Starting with the first classpath entry, the class loader visits each specified directory or archive file attempting to find the class to load. The first class it finds with the proper name is loaded, and any remaining classpath entries are ignored.

Sounds simple, right?

Classpath trickery
Whether they would admit it or not, beginner and veteran Java developers alike have at some point (usually at the worst possible moment!) been tricked by the onerous classpath. As the number of dependent third-party and user-defined classes increases for an application, and the classpath becomes a dumping ground for every conceivable directory and archive file, it's not always obvious which class the class loader will load first. This is especially true in the unfortunate event that the classpath contains duplicate class entries. Remember, the class loader loads the first properly named class it finds in the classpath and effectively "hides" all other properly named classes of lower precedence.

It's all too easy to fall victim to this classpath trickery. After a long day of slaving over a hot keyboard, you append a directory to the classpath in an attempt to get the latest and greatest version of a class loaded into the application, while unaware that another version of the class is located in a directory of higher precedence in the classpath. Gotcha!

JWhich: A simple classpath tool
The precedence problem inherent in a flat path declaration is not unique to the Java classpath. To find a solution to the problem requires only that you stand on the shoulders of legendary software giants. The Unix operating system's which command takes a name and displays the pathname of the file that would be executed had the name been issued as a command. It essentially traverses the PATH environment variable to locate the first occurrence of the command. That sounds like a powerful tool for managing the Java classpath, as well. Inspired by that notion, I set about writing a Java utility that could take a Java class name and display the absolute pathname of the class file that the class loader would load, as prescribed by the classpath.

The following example use of JWhich displays the absolute pathname of the first occurrence of the com.clarkware.ejb.ShoppingCartBean class to be loaded by the class loader, which happens to be in a directory:


    > java JWhich com.clarkware.ejb.ShoppingCartBean

    Class 'com.clarkware.ejb.ShoppingCartBean' found in
    '/home/mclark/classes/com/clarkware/ejb/ShoppingCartBean.class'

The following example use of JWhich displays the absolute pathname of the first occurrence of the javax.servlet.http.HttpServlet class to be loaded by the class loader, which happens to be packaged in an archive file:


    > java JWhich javax.servlet.http.HttpServlet

    Class 'javax.servlet.http.HttpServlet' found in
    'file:/home/mclark/lib/servlet.jar!/javax/servlet/http/HttpServlet.class'

 

How JWhich works
To unambiguously determine which class will be loaded first in the classpath, you need to get inside the mind of the class loader. This isn't as difficult as it sounds -- you just ask it! The relevant source code for JWhich follows. For the complete source code, see Resources.


1:   public class JWhich {
2:
3:     /**
4:      * Prints the absolute pathname of the class file
5:      * containing the specified class name, as prescribed
6:      * by the current classpath.
7:      *
8:      * @param className Name of the class.
9:      */
10:     public static void which(String className) {
11:
12:      if (!className.startsWith("/")) {
13:        className = "/" + className;
14:      }
15:      className = className.replace('.', '/');
16:      className = className + ".class";
17:
18:      java.net.URL classUrl =
19:        new JWhich().getClass().getResource(className);
20:
21:      if (classUrl != null) {
22:        System.out.println("/nClass '" + className +
23:          "' found in /n'" + classUrl.getFile() + "'");
24:      } else {
25:        System.out.println("/nClass '" + className +
26:          "' not found in /n'" +
27:          System.getProperty("java.class.path") + "'");
28:      }
29:    }
30:
31:    public static void main(String args[]) {
32:      if (args.length > 0) {
33:        JWhich.which(args[0]);
34:      } else {
35:        System.err.println("Usage: java JWhich <classname>");
36:      }
37:    }
38:  }
    

 

First, you need to massage the class name a bit to gain class loader acceptance (lines 12-16). Prepending a "/" to the class name instructs the class loader to match the class name verbatim within the classpath, rather than trying to implicitly prepend the package name of the invoking class. Converting each occurrence of "." to "/" formats the class name as a valid URL resource name required by the class loader.

Next, the class loader is interrogated (lines 18-19) for the resource matching the properly formatted class name. Every Class object maintains a reference to the ClassLoader object that loaded it, so the class loader that loaded the JWhich class itself is interrogated here. The Class.getResource() method actually delegates to the class loader that loaded the class, returning a URL for reading the class file resource, or null if a class file resource with the specified class name could not be found in the current classpath.

Finally, the absolute pathname of the class file containing the specified class name is displayed, if it was found in the current classpath (lines 21-24). As a debugging aid, if the class file was not found in the current classpath, you obtain the value of the java.class.path system property to display the current classpath (lines 24-28).

It's easy to imagine how this simple chunk of code could be invoked in a Java servlet using the servlet engine's classpath or an Enterprise JavaBean (EJB) using the EJB server's classpath. If the JWhich class were loaded by the custom class loader in a servlet engine, for example, then the servlet engine's class loader would be used to find classes. If the servlet engine's class loader is unable to locate a class, it will delegate to its parent class loader. In general, when JWhich is loaded by a class loader, it's able to find all classes loaded by its class loader or any parent class loaders.

Conclusion
If necessity is the mother of all invention, then a tool that helps manage the Java classpath is long overdue. Java-related newsgroups and mailing lists are chock full of questions related to the classpath. We need to lower the barrier to entry for new developers so we can all continue working at higher levels of abstraction. JWhich is a simple, yet powerful, tool that will help you master the Java classpath in any environment.

About the author
Mike Clark is an independent consultant for Clarkware Consulting, specializing in Java-based architecture, design, and development using J2EE technologies. He recently completed the development and deployment of a business-to-business (B2B) XML exchange server and is currently a consultant for a project building a J2EE performance management product.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值