tags:

views:

7970

answers:

6

Why is it so hard to do this in Java? If you want to have any kind of module system you need to be able to load jars dynamically. I'm told there's a way of doing it by writing your own ClassLoader, but that's a lot of work for something that should (in my mind at least) be as easy as calling a method with a jar file as its argument.

Any suggestions for simple code that does this?

P.S.: I know some see it as lame to answer your own questions, but I figured I'd do that so that a better one could bubble up past it.

+4  A: 

I found this solution a while back but it's so hackish I'm a little embarrassed to post it here. For one it uses reflection to bypass encapsulation, and it uses deprecated methods.

But... It works flawlessly.

public class ClassPathHack {
    private static final Class[] parameters = new Class[] {URL.class};

    public static void addFile(String s) throws IOException
    {
        File f = new File(s);
        addFile(f);
    }

    public static void addFile(File f) throws IOException
    {
        //f.toURL is deprecated
        addURL(f.toURL());
    }

    public static void addURL(URL u) throws IOException
    {
        URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Class sysclass = URLClassLoader.class;

        try {
            Method method = sysclass.getDeclaredMethod("addURL", parameters);
            method.setAccessible(true);
            method.invoke(sysloader, new Object[] {u});
        } catch (Throwable t) {
            t.printStackTrace();
            throw new IOException("Error, could not add URL to system classloader");
        }

    }
}
Allain Lalonde
This is the only solution that actually works. And you're right: *flawlessly*. I'm just worried that Sun will patch this up.
Paul Lammertsma
+14  A: 

You should take a look at OSGi, e.g. implemented in the Eclipse Platform. It does exactly that. You can install, uninstall, start and stop so called bundles, which are effectively JAR files. But it does a little more, as it offers e.g. services that can be dynamically discovered in JAR files at runtime.

Or see the specification for the Java Module System.

Martin Klinke
Neat. I'm not sure how easy it'd be to retro-fit an application for it. But it sounds very promising.Thanks
Allain Lalonde
+10  A: 

The reason it's hard is security. Classloaders are meant to be immutable; you shouldn't be able to willy-nilly add classes to it at runtime. I'm actually very surprised that works with the system classloader. Here's how you do it making your own child classloader:

URLClassLoader child = new URLClassLoader (myJar.toURL(), 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);

Painful, but there it is.

jodonnell
Only problem with this approach is that you need to know what classes are in what jars. As opposed to just loading a directory of jars and then instantiating classes. I am misunderstanding it?
Allain Lalonde
This method works great when running in my IDE, but when I build my JAR I get a ClassNotFoundException when calling Class.forName().
+2  A: 

Best I've found is org.apache.xbean.classloader.JarFileClassLoader

Here's a short method I've used in the past, to create a class loader from all the lib files in a specific directory

public void initialize(String libDir) throws Exception {
File dependencyDirectory = new File(libDir);
File[] files = dependencyDirectory.listFiles();
ArrayList<URL> urls = new ArrayList<URL>();
for (int i = 0; i < files.length; i++) {
    if (files[i].getName().endsWith(".jar")) {
 urls.add(files[i].toURL());
 //urls.add(files[i].toURI().toURL());
    }
}
classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(), 
 urls.toArray(new URL[urls.size()]), 
 GFClassLoader.class.getClassLoader());
}

Then to use the classloader, just do classLoader.loadClass(name);

+5  A: 

How about the JCL class loader framework? i have to admit, i havent used it, but looks promising.

usage example:

JarClassLoader jcl = new JarClassLoader();  
jcl.add("myjar.jar"); //Load jar file  
jcl.add(new URL("http://myserver.com/myjar.jar")); //Load jar from a URL  
jcl.add(new FileInputStream("myotherjar.jar")); //Load jar file from stream  
jcl.add("myclassfolder/"); //Load class folder  
jcl.add("myjarlib/"); //Recursively load all jar files in the folder/sub-folder(s)  

JclObjectFactory factory = JclObjectFactory.getInstance();  

//Create object of loaded class  
Object obj = factory.create(jcl,"mypackage.MyClass");
Chris
+2  A: 

Here is a version without deprecated I modified the original to remove deprecated.

/**************************************************************************************************
 * Copyright (c) 2004, Federal University of So Carlos                                           *
 *                                                                                                *
 * All rights reserved.                                                                           *
 *                                                                                                *
 * Redistribution and use in source and binary forms, with or without modification, are permitted *
 * provided that the following conditions are met:                                                *
 *                                                                                                *
 *     * Redistributions of source code must retain the above copyright notice, this list of      *
 *       conditions and the following disclaimer.                                                 *
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of   *
 *     * conditions and the following disclaimer in the documentation and/or other materials      *
 *     * provided with the distribution.                                                          *
 *     * Neither the name of the Federal University of So Carlos nor the names of its            *
 *     * contributors may be used to endorse or promote products derived from this software       *
 *     * without specific prior written permission.                                               *
 *                                                                                                *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS                            *
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT                              *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR                          *
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR                  *
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,                          *
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,                            *
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR                             *
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF                         *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING                           *
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS                             *
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
 **************************************************************************************************/
/*
 * Created on Oct 6, 2004
 */
package tools;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Useful class for dynamically changing the classpath, adding classes during runtime. 
 * @author unknown
 */
public class ClasspathHacker {
    /**
     * Parameters of the method to add an URL to the System classes. 
     */
    private static final Class<?>[] parameters = new Class[]{URL.class};

    /**
     * Adds a file to the classpath.
     * @param s a String pointing to the file
     * @throws IOException
     */
    public static void addFile(String s) throws IOException {
        File f = new File(s);
        addFile(f);
    }//end method

    /**
     * Adds a file to the classpath
     * @param f the file to be added
     * @throws IOException
     */
    public static void addFile(File f) throws IOException {
        addURL(f.toURI().toURL());
    }//end method

    /**
     * Adds the content pointed by the URL to the classpath.
     * @param u the URL pointing to the content to be added
     * @throws IOException
     */
    public static void addURL(URL u) throws IOException {
        URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
        Class<?> sysclass = URLClassLoader.class;
        try {
            Method method = sysclass.getDeclaredMethod("addURL",parameters);
            method.setAccessible(true);
            method.invoke(sysloader,new Object[]{ u }); 
        } catch (Throwable t) {
            t.printStackTrace();
            throw new IOException("Error, could not add URL to system classloader");
        }//end try catch        
    }//end method

    public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
        addFile("C:\\dynamicloading.jar");
        Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
        DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
        instance.test();

    }
}
Jonathan Nadeau