tags:

views:

89

answers:

1

I know I'm missing something basic. I'm trying to dynamically compile a class and then reload it after a change. The following code works. However, when I call it twice (once after changing the .java), the class definition does not update. What am I missing?

File file = new File("/eraseme/Eraseme.java");
File[] files = new File[] {file};
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();        
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(files);
CompilationTask task = compiler.getTask(null, fileManager, null, null,null,compilationUnits);
boolean madeIt = task.call(); // works

File classFile = new File("/eraseme/Eraseme.class");
URL url = classFile.toURL();
URL[] urls = new URL[] { url };
ClassLoader cl = new URLClassLoader(urls);   
Class cls = cl.loadClass("Eraseme");
TestApi test = (TestApi) cls.newInstance();
System.out.println(test.getVersion());
+2  A: 

What you're missing is that the urls passed to URLClassLoader should be directories that are then searched. What's happening is that the URLClassLoader that you're constructing isn't actually finding the .class file, but is failing to find the class and is falling back to having the parent classloader load the class.

Specifically, try modifying the second bit of your code to be:

File classFile = new File("/eraseme");
URL url = classFile.toURL();
URL[] urls = new URL[] { url };
ClassLoader cl = new URLClassLoader(urls);      
Class cls = cl.loadClass("Eraseme");  // will try to load /eraseme/Eraseme.class
TestApi test = (TestApi) cls.newInstance();
System.out.println(test.getVersion());

Note that if your class is called (with the package) com.mycompany.erasestuff.EraseMe, then the classloader will look in the file /eraseme/com/mycompany/erasestuff/EraseMe.class

Note that when you do this, you don't want the system classloader to be able to load the EraseMe class. The system classloader will take precedence, and your URLClassloader won't load anything. (The unfortunate thing is that when you say to a classloader "load this class", it first checks whether its parent classloader can load the class, then it tries to load it itself. If the system classloader can load this class, you'll never get a different version)

Daniel Martin
Thanks Daniel. Still stuck: even deleting the classfile (always generated on first run) does not change the outcome. I'm using no package names (default package) and I've changed to using File("/eraseme"), and that's where the class is... still doesn't change on update.
Yar
What's your system classpath? Does the system classpath include "/eraseme"?
Daniel Martin
Ah... good point. I had included it for the Eclipse project itself. I've removed that path from the project and with your fix, the code works perfectly. THANK you for your help!
Yar
"The unfortunate thing ...". Actually, it is fortunate, otherwise class loading would allow applications to blow away sandbox security by loading new versions of (for example) java.security.* classes.
Stephen C
Stephen C: it's not a security restriction, except in the sense of being a "secure by default" design decision, since any code that is allowed to instantiate a new classloader and load classes through it could also instantiate a classloader with an alternate resolution order. (I forget the name of the method, but it's a straightforward override) It is unfortunate that using that alternate class resolution order isn't at all an easy thing to do.
Daniel Martin