Results 1 to 9 of 9
  1. #1
    rc_coder is offline Member
    Join Date
    Sep 2010
    Posts
    11
    Rep Power
    0

    Default Adding to ClassPath at runtime - ClassPathHacker.java - safe?

    I tried to post this on the Sun forums (it's where I found ClassPathHacker.java) but had some problems, they're migrating to Oracle forums in 2 days anyway.

    First why I want to do this.
    So sometimes you'd like to be able to add a jar or some classes to the ClassPath while a program is running.
    For example for me, I am using a particular plugin framework for my application, I have defined a GeneralPlugin interface with a method doX() which all plugins must implement.
    Some guy comes along and adds his plugin pluginA to my application.
    Later my application receives a request which gets mapped (I'll omit the details) to a string representing pluginA, i.e. "com.someguy.plugins.pluginA".
    In my main class I want to create an instance of pluginA from the string and call doX().
    So I would have something like
    Java Code:
    GeneralPlugin p = (GeneralPlugin) PluginManager.getPlugin(Class.forName("com.someguy.plugins.pluginA"));
    p.doX();
    This would call the correct doX() method in pluginA.
    But of course when you try to create the plugin you will get a class not found exception because "com.someguy.plugins.pluginA" is not in your classpath.
    java.lang.ClassNotFoundException: com.someguy.plugins.pluginA

    So I need to dynamically ad it.

    And now my question, on the Java forums I found this solution.

    Java Code:
    public class ClassPathHacker {
     
    private static final Class[] parameters = new Class[]{URL.class};
     
    public static void addFile(String s) throws IOException {
        File f = new File(s);
        addFile(f);
    }//end method
     
    public static void addFile(File f) throws IOException {
        addURL(f.toURL());
    }//end method
     
     
    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
     
    }//end class
    It works, however some people expressed concerns about using the Reflection API in such away, I have some reservations myself. Also some people are concerned about the assumption that the System class loader is of type URLClassLoader, apparently that's not always the case.

    So can anyone clarify if the above method is ok to use? If not, what's the alternative.


    I tried creating my own loader by extending URLClassLoader as follows:
    Java Code:
    public class PluginClassLoader extends URLClassLoader {
    
        public PluginClassLoader() {
            super(new URL[] {}, ClassLoader.getSystemClassLoader());
        }
    
        public void addFile(URL file) {
        	System.out.println("Adding: "+file);
            super.addURL(file);
        }
    
    
    }
    But it doesn't work, and I don't know why.

    p.s. does this forum have a JAVA tag? Couldn't find any.
    Last edited by rc_coder; 09-22-2010 at 05:06 PM. Reason: Missing line of code

  2. #2
    masijade is offline Senior Member
    Join Date
    Jun 2008
    Posts
    2,571
    Rep Power
    9

    Default

    Please explain, in detail, what "doesn't work" means.

  3. #3
    rc_coder is offline Member
    Join Date
    Sep 2010
    Posts
    11
    Rep Power
    0

    Default

    I'm afraid there's not really much detail to be given. Nothing unexpected or half expected happens.
    It correctly prints that I am adding the file to the classpath
    Java Code:
    Adding: file:/C:/pluginA.jar
    Then I simply get a java.lang.ClassNotFoundException when trying to load the newly addes class from the string com.someguy.plugins.pluginA.
    If you want to see the full trace, although I doubt it will make any difference because it is the exact same as if I didn't attempt to load any classes, this is it:
    Java Code:
    java.lang.ClassNotFoundException: com.someguy.plugins.pluginA
    	at java.net.URLClassLoader$1.run(Unknown Source)
    	at java.security.AccessController.doPrivileged(Native Method)
    	at java.net.URLClassLoader.findClass(Unknown Source)
    	at java.lang.ClassLoader.loadClass(Unknown Source)
    	at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    	at java.lang.ClassLoader.loadClass(Unknown Source)
    	at java.lang.ClassLoader.loadClassInternal(Unknown Source)
    	at java.lang.Class.forName0(Native Method)
    	at java.lang.Class.forName(Unknown Source)
    Anyway my question is what do you think ClassPathHacker? If I use that class to acomplish the same thing, it works fine, I get no Exception and the correct doX() is called withour problems.
    Of course if someone knows why my ClassLoader doesn't work that's great, because I'd much rather use that than a reflections hack.
    Last edited by rc_coder; 09-22-2010 at 07:07 PM.

  4. #4
    masijade is offline Senior Member
    Join Date
    Jun 2008
    Posts
    2,571
    Rep Power
    9

    Default

    Try setting that ClassLoader as the context class loader (see the Thread API docs).

  5. #5
    rc_coder is offline Member
    Join Date
    Sep 2010
    Posts
    11
    Rep Power
    0

    Default

    Quote Originally Posted by masijade View Post
    Try setting that ClassLoader as the context class loader (see the Thread API docs).
    You really want people to work for a solution :) good for learning I suppose, but I already knew about context class loader so didn't have to trawl APIs, doesn't help I'm afraid :( same stack trace as before.
    I presume you ment:
    Java Code:
    Thread.currentThread().setContextClassLoader(myClassLoader);
    Also tried using the current context class loader as the parameter to the URL constructor in my Plugin class loader, but no good either.

  6. #6
    masijade is offline Senior Member
    Join Date
    Jun 2008
    Posts
    2,571
    Rep Power
    9

    Default

    Here, see what little nuggets you can get from this. This is a class I wrote (just playing around) to take any jarfile, read its manifest, load its defined classpath, including that jar, itself, and execute the mainclass main method, in its own thread with its own context loader.

    Java Code:
    package test;
    
    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.jar.JarFile;
    import java.util.jar.Manifest;
    
    public class JarStarter extends Thread {
    
    	private File f;
    	private JarFile jf;
    
    	public JarStarter (File f) throws IOException {
    		this.f = f;
    		try {
    			jf = new JarFile(f);
    		} catch (IOException ioe) {
    			System.err.println("Error loading JarFile.");
    			jf = null;
    			throw ioe;
    		}
    	}
    
    	public void run() {
    		if (jf == null) return;
    		try {
    			String mainClass = getMainClass();
    			if ((mainClass == null) || (mainClass.trim().length() == 0)) return;
    			ClassLoader cl = createClassLoader(getClassPath());
    			if (cl == null) return;
    			setContextClassLoader(cl);
    			Class<?> c = cl.loadClass(mainClass);
    			c.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
    		} catch (InvocationTargetException ite) {
    			ite.printStackTrace();
    		} catch (IllegalAccessException iae) {
    			iae.printStackTrace();
    		} catch (NoSuchMethodException nsme) {
    			nsme.printStackTrace();
    		} catch (ClassNotFoundException cnfe) {
    			cnfe.printStackTrace();
    		} catch (IOException ioe) {
    			ioe.printStackTrace();
    		} finally {
    			try { jf.close(); } catch (IOException ioe) {}
    		}
    	}
    
    	private String getMainClass() throws IOException {
    		Manifest man = jf.getManifest();
    		return man.getMainAttributes().getValue("Main-Class");
    	}
    
    	private String[] getClassPath() throws IOException {
    		Manifest man = jf.getManifest();
    		String classpath = man.getMainAttributes().getValue("Class-Path");
    		if (classpath == null) return new String[0];
    		return classpath.split("\\s+");
    	}
    
    	private ClassLoader createClassLoader(String[] items) {
    		String dir = f.getParentFile().getAbsolutePath();
    
    		List<URL> urls = new ArrayList<URL>();
    		try {
    			urls.add(f.toURL());
    			for (String item : items) {
    				urls.add(new File(dir + item).toURL());
    			}
    		} catch (MalformedURLException murle) {
    			murle.printStackTrace();
    			return null;
    		}
    
    		return new URLClassLoader(urls.toArray(new URL[0]), getClass().getClassLoader());
    	}
    
    	public static void main(String[] args) {
    		try {
    			JarStarter st = new JarStarter(new File(args[0]));
    			st.start();
    		} catch (Exception ioe) {
    			ioe.printStackTrace();
    		}
    	}
    }

  7. #7
    rc_coder is offline Member
    Join Date
    Sep 2010
    Posts
    11
    Rep Power
    0

    Default

    Quote Originally Posted by masijade View Post
    Here, see what little nuggets you can get from this. This is a class I wrote (just playing around) to take any jarfile, read its manifest, load its defined classpath, including that jar, itself, and execute the mainclass main method, in its own thread with its own context loader.
    I took your class, messed with it, mashed it with mine, did pretty much anything you could think of and yours still worked even when using mine as the classloader. So basically in one file everything works, in the other it doesn't, so there must be something else going on.

    Spent the whole morning, downloaded all the Java source files and source of the plugin framework I'm using, after debugging it and troucing through it I think I've realised the problem, but fat lot of good it does me.

    Basically the opensource Plugin framework I'm using uses it's own complicated class loading techniques and uses a 3rd party class loading api called ClassWorld. When these are being used they only have the classpath that was set at runtime, i.e. only what the System class loader says, our custom class loaders may work in our indivdual programs but when a 3rd party api is used it may have seperate threads running and has it's own loaders which refrence the System class loader, and not our classloaders.

    This also explains why the hack works, the hack is refrencing the system class loader directly and adding a class path entry to that. In the 3rd party api when it's doing all it's class loading it refrences the system classloader so it sees the class path we added.

    So basically if I want to use my own loader I'll have to go modify the 3rd party plugin framework and possibly the 3rd party ClassLoader api it uses.
    i.e. potentially a massive amount of work :)

    For now I'll just have to stick to the hack, if anyone knows of an ingenious alternative solution be sure to post it!!

    Perhaps at some point if I get the time I might try to sort something better out, because as I said the hack uses reflection in a dubious way and assumes the System class loader is an URLClassloader (btw anyone know a situation when the sys loader ISN'T an URLClassLoader).

    Thanks for your input masijade!

  8. #8
    rc_coder is offline Member
    Join Date
    Sep 2010
    Posts
    11
    Rep Power
    0

    Default

    Oh ya, and if anyone has any opionions on ClassPathHacker please add them, want to see if other people have the same reservations I do.

    How about you masijade, any opinion or comment on it?

  9. #9
    masijade is offline Senior Member
    Join Date
    Jun 2008
    Posts
    2,571
    Rep Power
    9

    Default

    Messing with the system classpath in a running application is never a good idea, IMHO.

Similar Threads

  1. checking runtime classpath in Eclipse
    By debu37 in forum Eclipse
    Replies: 3
    Last Post: 11-23-2012, 01:29 AM
  2. Adding jars at runtime
    By tbar0711 in forum Enterprise JavaBeans (EJB)
    Replies: 0
    Last Post: 02-17-2010, 07:25 PM
  3. Adding jar to classpath.
    By dudejonne in forum New To Java
    Replies: 11
    Last Post: 11-04-2009, 04:58 PM
  4. Adding component at runtime
    By Beju in forum Java Applets
    Replies: 5
    Last Post: 06-16-2009, 11:28 AM
  5. Adding JARs to the classpath at runtime?
    By johann_p in forum Advanced Java
    Replies: 1
    Last Post: 06-26-2007, 10:42 PM

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •