I've seen a lot of forum posts about how to modify the classpath at runtime and a lot of answers saying it can't be done. I needed to add JDBC driver JARs at runtime so I figured out the following method.
The system classloader (ClassLoader.getSystemClassLoader()) is a subclass of URLClassLoader. It can therefore be casted into a URLClassLoader and used as one.
URLClassLoader has a protected method addURL(URL url), which you can use to add files, jars, web addresses - any valid URL in fact.
Since the method is protected you need to use reflection to invoke it.
Here's some code for a class which adds a File or URL to the classpath:
publicclass ClassPathHacker {
privatestaticfinal Class[] parameters = new Class[]{URL.class};
publicstaticvoid addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}//end method
publicstaticvoid addFile(File f) throws IOException {
addURL(f.toURL());
}//end method
publicstaticvoid 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();
thrownew IOException("Error, could not add URL to system classloader");
}//end try catch
}//end method
}//end class
Hi,
I don't understand how I'm supposed to use this class. I mean, when I have a class 'dir_1.dir_2.MyClass' in the classpath 'c:\myclasspath\' what should I do to make an instance of myClass?
Antony Miguel, your name should be up in lights on Broadway. Well at least I hope Sun made you an offer of employment.
When I first needed to dynamically add a jarfile to the classpath I checked the API for ClassLoader confidently expecting to find an addResource(URL) or setResource(URL) method. I couldn't believe it when I discovered there is no standard method of modifying the JRE's classpath.
Your ClassPathHacker is now a standard in our "foundation" classes.
Antony Miguel, you are the genius! I spent two weeks fighting with multiple classloaders - and still couldn't get everything done the way I needed.
This is just amazing!
One reservation, though - are we guaranteed that the system classloader will stay a subclass of URLClassLoader in the future versions of Java on all platforms (well, at least, NT, Mac, Linux, and Solaris)?
Why not use new URLClassLoader directly and then do getResource() for any file needed at runtime from the newly added resource? This way you dont have to worry about future implementation of ClassLoader.getSystemClassLoader() to return URLClassLoader
Ashish
I have to agree with arjput. Hacking and using reflection to make use of a protected method is not a very good thing to do. It works, yes, but the reflection API wasn't meant to hack into system classes such as the application classloader.
A better approach is to launch your app with your own classloader. I saw an article on this onetime, I think it's something like -Dbootstrap.classloader=your-classloader-class but I forget.
The approach I prefer is a launching app to create a custom loader (that extend URLClassLoader) and load all my classes from that point. One advantage to this is you can "reload" your application at runtime without having to stop/start the JVM and your app.
An even better approach is to develop with a plugin engine like mine at www.sourceforge.net/genpluginengine. It provides a "container" for plugins that can depend on one another, load/unload/reload, provide services, and so forth. The benefit is not only the container managed plugin lifecycle, but the easier development across teams, easier management of specific modules of an application, extensibility it adds to an applcation, and the development time it saves. Plugins are much smaller than the whole (usually) so take less time to build and with the (soon to be added) watch and reload system, modifying a plugin get's it reloaded at runtime so that you no longer have to restart your jvm and app, possibly log in, etc. IT reloads the plugin and any affected dependent plugins, as necessary at runtime.
There are lots of ways to load classes dynamically at runtime. The purpose of the code above is to be able to easily load classes at runtime as if they had been loaded as normal from the very beginning. This might sound odd but if you're using third party code which you don't have control over (e.g. JDBC drivers in my original example) then it can easily be a requirement.
If you don't have control over the command line or you don't want to make restrictions about command line usage you can't specify a proprietary classloader on the command line. If the (potentially third party) code you are loading does it's own complex dynamic class loading then it may expect to get it's classes and resources from the system classloader (e.g. any code that does ClassLoader.getSystemClassLoader() to get class references or resources etc).
The code above is not necessarily the best way for everyone to dynamically load their code - but using it guarantees the dynamically loaded code will work as it would if it had been loaded by the system classloader at the very beginning. This is particularly important if you're loading third party code which you can't fully test with some other dynamic loading method.
Hope this clears up why I used reflection and the system classloader etc rather than some other method. The point is not to load code dynamically in the nicest way, but to load it so as to be indistinguishable from loading it as normal.
Fair play, you're a star!
I was getting there, had figured that the system class loader was a URLClassLoader, but you've made it clear and simple.
You've saved me from dumping that code, and trying yet another route.
I didn't know about Method:setAccessible(boolean). I've now got a handful of uses for this method, specifically with some Eclipse classes that I now realise that won't need to subclass any more.
The system classloader (ClassLoader.getSystemClassLoader()) is a subclass of
URLClassLoader.
Not always. For most applications, yes. So to be careful, you should do an "instanceof" check, and throw an exception (or log an error) if it's not.
method.setAccessible(true);
But ooh, this is a sneaky way around the API protections! Naturally, folks, don't always depend on this working, OK? It's much better to use the documented APIs to achieve this.
This topic has
59
replies
on
4
pages.
1
|
2
|
3
|
4
|
Next »