I have two jar files, Foo.jar
and Bar.jar
. Inside Foo.jar
I have a class Foo
with a main
method which looks like this:
public class Foo {
public static void main(String[] args) {
Bar bar = new Bar();
bar.sayHello();
new File("./Bar.jar").deleteOnExit();
}
}
The class Bar
lives in Bar.jar
and looks like this:
public class Bar {
public void sayHello() {
System.out.println("Hello! I'm Bar!");
}
}
The intention here is that Foo.jar
will use a class in Bar.jar
and afterwards it will delete Bar.jar
. With the above implementation this is the output when executed:
$ java -jar Foo.jar
Hello! I'm Bar!
but Bar.jar
does not get deleted. Here I've tried both new File("./Bar.jar").deleteOnExit();
and new File("./Bar.jar").delete();
in class Foo
.
If I change class Bar
to close its ClassLoader like this:
public class Bar {
public void sayHello() {
System.out.println("Hello! I'm Bar!");
URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader();
try {
classLoader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
and run Foo.jar
again, this is the output:
$ java -jar Foo.jar
Hello! I'm Bar!
the output is the same as in the previous example, but in this case Bar.jar
is deleted, which is great, but the problem now is that if I do something like this in class Foo
:
9 public class Foo {
10 public static void main(String[] args) {
11 Bar bar = new Bar();
12 bar.sayHello();
13
14 new File("./Bar.jar").deleteOnExit();
15
16 SimpleFileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
17 };
18 }
19 }
this becomes the output of executing Foo.jar
:
$ java -jar Foo.jar
Hello! I'm Bar!
Exception in thread "main" java.lang.NoClassDefFoundError: playground/modulae/Foo$1
at playground.modulae.Foo.main(Foo.java:16)
Caused by: java.lang.ClassNotFoundException: playground.modulae.Foo$1
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
In this case, Bar.jar
still gets deleted, but when I try to create a new SimpleFileVisitor
it crashes with a NoClassDefFoundError
. It seems like closing the ClassLoader in Bar.jar
causes things to get a bit messed up for Foo.jar
as well, but I have not found any other way that lets me delete Bar.jar
from Foo.jar
.
The general structure of this example closely resembles what happens in the real application, which uses java.nio.file.SimpleFileVisitor
later on. I've tried creating a few other classes instead of java.no.file.SimpleFileVisitor
and none of them crash, but I guess it depends on whether or not they have been loaded by the ClassLoader before Foo
calls Bar
.
After messing around with class loaders (which I still don't fully understand) I have something that seems to be sufficient. However, I fear that I'm doing something a bit dodgy and that there is a more 'correct' way of handling the class loader manipulation that I'm doing.
So the setup now is:
Foo
with a main
method and a speak()
method in Foo.jar
. Bar
with a simple sayHello()
method in Bar.jar
. main
function I create an instance foo
of Foo
with a custom class loader and call foo.speak()
foo.speak()
I create a instance bar
of Bar
like this: Bar bar = new Bar()
<- Notice that there is no reflection or anything fancy here. Then I call bar.sayHello()
bar.sayHello()
I call this.getClass().getClassLoader().close()
main
which deletes Bar.jar
And then the code:
public class Foo {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
System.out.println("Foo.main: Class path: " + System.getProperty("java.class.path"));
URLClassLoader initialClassLoader = (URLClassLoader) Foo.class.getClassLoader();
System.out.println("Foo.main: initialClassLoader = " + initialClassLoader);
System.out.println("Foo.main: initialClassLoader.getURLs() = " + Arrays.toString(initialClassLoader.getURLs()));
File fooJar = new File("./Foo.jar");
File barJar = new File("./Bar.jar");
CustomClassLoader customClassLoader = new CustomClassLoader(new URL[]{fooJar.toURI().toURL(), barJar.toURI().toURL()});
System.out.println("Foo.main: customClassLoader = " + customClassLoader);
System.out.println("Foo.main: customClassLoader.getURLs() = " + Arrays.toString(customClassLoader.getURLs()));
Class<?> fooClass = customClassLoader.findClass("Foo");
Object foo = fooClass.newInstance();
Method speak = fooClass.getMethod("speak");
speak.invoke(foo);
barJar.deleteOnExit();
SimpleFileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
};
}
public void speak() {
System.out.println("Foo.speak()");
URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader();
System.out.println("Foo.speak(): classLoader = " + classLoader);
System.out.println("Foo.speak(): classLoader.getURLs() = " + Arrays.toString(classLoader.getURLs()));
Bar bar = new Bar();
bar.sayHello();
}
}
public class Bar {
public void sayHello() {
System.out.println("Bar.sayHello()");
URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader();
System.out.println("Bar.sayHello(): classLoader = " + classLoader);
System.out.println("Bar.sayHello(): classLoader.getURLs() = " + Arrays.toString(classLoader.getURLs()));
try {
classLoader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class CustomClassLoader extends URLClassLoader {
public CustomClassLoader(URL[] urls) {
super(urls);
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
$ java -jar Foo.jar
Foo.main: Class path: Foo.jar
Foo.main: initialClassLoader = sun.misc.Launcher$AppClassLoader@1f96302
Foo.main: initialClassLoader.getURLs() = [file:/Foo.jar]
Foo.main: customClassLoader = CustomClassLoader@a298b7
Foo.main: customClassLoader.getURLs() = [file:/Foo.jar, file:/Bar.jar]
Foo.speak()
Foo.speak(): classLoader = CustomClassLoader@a298b7
Foo.speak(): classLoader.getURLs() = [file:/Foo.jar, file:/Bar.jar]
Bar.sayHello()
Bar.sayHello(): classLoader = CustomClassLoader@a298b7
Bar.sayHello(): classLoader.getURLs() = [file:/Foo.jar, file:/Bar.jar]
and Bar.jar
is deleted.
As we see, Bar
can be referenced from within Foo.jar
without any reflection, it has been loaded with the correct CustomClassLoader
and Bar.jar
can successfully be deleted from Foo
after having called bar.sayHello()
.
Now, this solves my problem. However, I'm not confident that I have solved the CustomClassLoader
correctly. In fact, the only thing that CustomClassLoader
effectively does is make findClass()
public. I tried a whole bunch of different ways of implementing loadClass()
, but could never get it to work and just by accident I tried findClass()
instead and it worked.
Does anyone have any opinions on how CustomClassLoader
is used?
OK, so here what I would do:
I would define an interface to use by Foo
, and implement by Bar
:
public interface BarIntf {
public void sayHello();
}
The interface must be in Foo.jar.
The Bar
class would implement BarIntf
:
public class Bar implements BarIntf {
@Override
public void sayHello() {
System.out.println("Hello! I'm Bar!");
}
}
Now, instead of putting both Foo.jar and Bar.jar in the classpath, I would put only Foo.jar, and create a class loader in program Foo
just to load the Bar
class.
It looks like this:
public class Foo {
public static void main(String[] args) {
File jar = new File("./Bar.jar");
try (URLClassLoader cl = new URLClassLoader(
new URL[] {jar.toURI().toURL()})) {
Class<?> clazz = cl.loadClass("bar.Bar");
BarIntf bar = (BarIntf)clazz.newInstance();
bar.sayHello();
// ...a lot of very useful things
} catch (IOException | ClassNotFoundException
| InstantiationException | IllegalAccessException ex) {
Logger.getLogger(Foo.class.getName()).log(Level.SEVERE, null, ex);
} finally {
jar.delete();
}
}
}
UPDATE:
With reflection (no interface required):
public class Foo {
public static void main(String[] args) {
File jar = new File("./Bar.jar");
try (URLClassLoader cl = new URLClassLoader(
new URL[] {jar.toURI().toURL()})) {
Class<?> clazz = cl.loadClass("bar.Bar");
Method method = clazz.getDeclaredMethod("sayHello");
Object bar = clazz.newInstance();
method.invoke(bar);
// ...a lot of very useful things
} catch (IOException | ClassNotFoundException
| InstantiationException | IllegalAccessException
| NoSuchMethodException | SecurityException
| InvocationTargetException ex) {
Logger.getLogger(Foo.class.getName()).log(Level.SEVERE, null, ex);
} finally {
jar.delete();
}
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.