views:

78

answers:

4

Conceptually, what am I doing when I'm loading a new Jar? Is URLClassloader the only choice? How should I form those URLs to point to a subdirectory containing more jars.

If anyone is feeling super-generous, some demonstration code to do the following would be really helpful (let's assume "jars/A.jar" contains "myClass" which we want to instantiate):

1 load some jar from a subdirectory
2 return a particular class from it
3 instantiate that class

Thanks! T

+3  A: 

Conceptually, what am I doing when I'm loading a new Jar?

Conceptually, you are not loading a new JAR. Rather, you are defining a class loader which will load code and other resources from the JAR file on demand.

Is URLClassloader the only choice?

In theory, you could implement your own subclass of ClassLoader, but that's not necessary for what you are trying to do.

How should I form those URLs to point to a subdirectory containing more jars.

This is the nub of your problem I think. The URLClassLoader constructors interpret the URL[] argument as follows:

"Any URL that ends with a '/' is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be downloaded and opened as needed."

In the first case of the quoted text above, the directory is assumed to be the root of tree containing resources to be loaded.

But I gather you are trying to set up a class loader that will load from all JAR files in a given directory. To do that, you need to:

  1. Read the directory and build a list of the File objects for any JAR files.
  2. Create an array to hold the same number of URL instances.
  3. For each JAR file File, use File.toURL() to create a URL and add to the array. (Using File.toURL() means that you will get kosher "file:" URLs for the JAR files that will work on your platform.)
  4. Create the URLClassLoader using the URL array.
Stephen C
A: 

If the list of jars is known then a potentially better/easier solution would be to just add a Class-Path entry to the parent jar's manifest. That way you avoid mucking around with custom classloaders completely.

Class-Path: jars/A.jar

Obviously this won't work where the dependent jars aren't known at build time.

Michael Rutherfurd
A: 

Really short version: when you load a new JAR, you're giving Java a new place to look for classes.

For a longer explanation, Wikipedia has an article related to this: http://en.wikipedia.org/wiki/Java_Classloader

Also, might try: http://onjava.com/pub/a/onjava/2005/01/26/classloading.html

URLClassLoader is not your only choice; check out http://download-llnw.oracle.com/javase/tutorial/deployment/jar/apiindex.html

Re: how URLs should be formed, from the Java documentation (http://download-llnw.oracle.com/javase/7/docs/api/java/net/URLClassLoader.html):

This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories. Any URL that ends with a '/' is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed.

Adapted from http://stackoverflow.com/questions/60764/how-should-i-load-jars-dynamically-at-runtime:

URLClassLoader child = new URLClassLoader (new URL("jars/A.jar"), this.getClass().getClassLoader());
Class classToLoad = Class.forName ("com.myClass", true, child);
Method method = classToLoad.getDeclaredMethod ("myMethod");
Object instance = classToLoad.newInstance ();
Object result = method.invoke (instance);

Also, the answer linked above lists a few more alternatives to URLClassLoader (they mention OSGi, JCL, and a few others, none of which I have any experience with or I'd say something here).

HTH

cubic1271
A: 

The easy way is to make sure the dependent JARs are in the classpath before starting the program.


If you want to load from a sub-directory, you need to determine the path of the JAR to give to the URLClassLoader. You can use something like this to get the path to your program:

new File(MyClass.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath())

It's a bit of a hack but I don't know of any official way to get the path to a Java program.


If you want to dynamically load a JAR such that you can use it as if you had specified it in the classpath, check out this thread. It's definitely in the realm of unsupported hack, but it's also simple and easy.

Modified version that I use:

import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Hack to modify classpath at runtime. Can be used to load JARs as if they were
 * loaded from the start.
 * 
 * Warning: May break if URLClassLoader changes
 * 
 * @author Antony Miguel
 */
public class ClasspathHacker {
    /**
     * Add a file to the classpath.
     * 
     * @param pPath
     *            path to the file
     */
    public static void addFile(String pPath) {
        File f = new File(pPath);
        addFile(f);
    }

    /**
     * Add a file to the classpath.
     * 
     * @param pFile
     *            the file to be added
     */
    public static void addFile(File pFile) {
        try {
            addURL(pFile.toURI().toURL());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    /**
     * Add the content pointed to by a URL to the classpath.
     * 
     * @param pURL
     *            the URL pointing to the content to be added
     */
    public static void addURL(URL pURL) {
        /*
         * Use reflection to call addURL on the system classloader, which we
         * expect to be a URLClassLoader
         */
        Method method;
        try {
            method = URLClassLoader.class.getDeclaredMethod("addURL",
                    new Class[] { URL.class });
            method.setAccessible(true);
            method.invoke(ClassLoader.getSystemClassLoader(),
                    new Object[] { pURL });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Peter Tseng