Wednesday, November 16, 2011

Add to the Java Classpath at Runtime

When writing an application, in any programming language, one often needs to load data from an external resource. This is typically a file on the filesystem, in which case one writes code to open the file using a filesystem path, and read the contents. The Java programming language lets you do this in a more abstract way, where instead of loading a file, or a device, you simply load a “resource”.

This makes this process more generic, more platform independent, and more device independent, because a “resource” can be loaded without hard-coding the type of resource it is, whether it’s a file, a device, network stream, or some other data source. Instead of creating a File object, giving it a path, and opening it, one only has to make a call to one of the ‘getResource’ methods. This makes your code more flexible and portable, because if the source of your data changes from a file to something else, you don’t have to change your code; you only have to change your class path.

The class path in Java is just like the PATH variable in a Windows or Unix command shell. When you run a command in a shell (without using the full path to it), the PATH variable is a list of directories used to look for that command. In Java, the class path is very similar, in that it is a list of locations used to look for a resource. Its standard use is in the loading of class files for the running application, hence its name. But it can be used to find any resource.

The standard way to set the class path is via the CLASSPATH environment variable or the ‘-cp’ command line option. But these won’t work if you ever need to change the class path programmatically at run time.

A Simple Example


Let’s say that you have some code that loads a configuration file from a location that’s relative to the current working directory. I could use a File object, but again, that’s not as flexible, and I’d like to be able to use the configuration loading in other situations. So, the answer is to add the current working directory to the class path, and I want to do it at runtime so I don’t have to always specify it in CLASSPATH or on the command line.

There are a couple of different ways to add to the class path, depending on how you plan to retrieve the resource.

1. Create a new context loader for the current thread

Thread.currentThread().setContextLoader(
   new URLClassLoader(
      new URL[] { file:///some/path/ }),
   Thread.currentThread().getContextClassLoader())
);
This solution will only work reliably if the resource is located using the same method:
Thread.currentThread().getContextClassLoader().getResource(lcFi lename);
It will also work for objects created after the context loader is set, but keeping track of when things are created is not always that easy.

2. Use reflection. Call the protected ‘addURL’ method on the system class loader, which is an instance of the URLCLassLoader class. This method may be considered by some to be a hack, because it may no work on all JVMs, and it’s using reflection instead of a standard API. But a path added this way will always work, because context class loaders and other class loaders in the hierarchy will defer to the system class loader when a resource cannot be found, so this is the most reliable way to add a path.
URLClassLoader.addURL(URL url)  – protected method

Method addUrl = 
   URLClassLoader.class.getMethod(“addURL”, new Class [] { URL.class });
addUrl.setAccessible(true);
addUrl.invoke(
   ClassLoader.getSystemClassLoader(), 
   new URL(“file://” + System.getProperty(“user.dir”) + “/”));

Easy!


Note that a directory entry must end with a slash (‘/’). If it doesn’t, then it will be treated like it’s a jar file, and the search for your resource will fail with little explanation. (One would think that it could tell the difference between a jar file and a directory, but I’m guessing there’s something about a filesystem directory that’s not platform independent enough. But I digress...).

Now, to get our configuration file, we can call one of the getResource methods:
String resource = “config/setting.properties”;
URL resourceUrl = ClassLoader.getSystemResource(resource);
InputStream in = this.getClass().getClassLoader().getResourceAsStream(resource);
Just make sure you understand the difference between the getResource method and the getResource method! :)



No comments: