我有两个jar文件,Foo.jar
和Bar.jar
。在Foo.jar
里面,我有一个带有main
方法的类Foo
,如下所示:
public class Foo {
public static void main(String[] args) {
Bar bar = new Bar();
bar.sayHello();
new File("./Bar.jar").deleteOnExit();
}
}
该类Bar
住在Bar.jar
,看起来像这样:
public class Bar {
public void sayHello() {
System.out.println("Hello! I'm Bar!");
}
}
这里的意图是Foo.jar
将在Bar.jar
中使用一个类,然后删除Bar.jar
。使用上述实现,这是执行时的输出:
$ java -jar Foo.jar
Hello! I'm Bar!
但Bar.jar
不会被删除。在这里,我在课堂上尝试了new File("./Bar.jar").deleteOnExit();
和new File("./Bar.jar").delete();
Foo
。
如果我更改类Bar
以像这样关闭其类加载器:
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();
}
}
}
并再次运行Foo.jar
,这是输出:
$ java -jar Foo.jar
Hello! I'm Bar!
输出与前面的示例相同,但在这种情况下Bar.jar
被删除,这很好,但现在的问题是,如果我在类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 }
这将成为执行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
在这种情况下,Bar.jar
仍然被删除,但是当我尝试创建新SimpleFileVisitor
时,它会崩溃并显示NoClassDefFoundError
.似乎关闭Bar.jar
中的类加载器也会导致事情变得有点混乱Foo.jar
,但我还没有找到任何其他方法可以让我从Foo.jar
中删除Bar.jar
。
此示例的一般结构与实际应用程序中发生的情况非常相似,实际应用程序中稍后将使用java.nio.file.SimpleFileVisitor
。我尝试创建一些其他类而不是java.no.file.SimpleFileVisitor
,但它们都没有崩溃,但我想这取决于它们是否在Foo
调用Bar
之前被类加载器加载
更新
在弄乱了类加载器(我仍然不完全理解(之后,我有一些东西似乎足够了。但是,我担心我正在做一些有点狡猾的事情,并且有一种更"正确"的方式来处理我正在做的类加载器操作。
所以现在的设置是:
- 我在
Foo.jar
中有一个main
方法和一个speak()
方法的类Foo
。 Bar.jar
中使用简单sayHello()
方法Bar
类。- 在
main
函数中,我使用自定义类装入器创建了一个Foo
foo
实例并调用foo.speak()
foo.speak()
我创建了一个实例bar
Bar
如下:Bar bar = new Bar()
<-请注意,这里没有反射或任何花哨的东西。然后我打电话给bar.sayHello()
- 在
bar.sayHello()
我称之为this.getClass().getClassLoader().close()
- 然后,所有内容都返回到删除
Bar.jar
的main
然后代码:
Foo.java (在 Foo.jar(
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();
}
}
酒吧.java (在酒吧.jar(
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();
}
}
}
CustomClassLoader (in Foo.jar(
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]
并删除Bar.jar
。
思潮
如我们所见,Bar
可以在没有任何反射的情况下从Foo.jar
内部引用,它已加载了正确的CustomClassLoader
,并且在调用bar.sayHello()
后Bar.jar
可以成功地从Foo
中删除。
现在,这解决了我的问题。但是,我不确定我是否正确解决了CustomClassLoader
。事实上,CustomClassLoader
唯一有效做的事情就是公开findClass()
。我尝试了一大堆不同的实现loadClass()
的方法,但永远无法让它工作,只是偶然地我尝试了findClass()
,它奏效了。
有人对如何使用CustomClassLoader
有任何意见吗?
好的,所以在这里我会做什么:
我会定义一个接口供Foo
使用,并由Bar
实现:
public interface BarIntf {
public void sayHello();
}
接口必须位于 Foo.jar 中。
Bar
类将实现BarIntf
:
public class Bar implements BarIntf {
@Override
public void sayHello() {
System.out.println("Hello! I'm Bar!");
}
}
现在,我不会将 Foo.jar 和 Bar.jar 放在类路径中,而是只放置 Foo.jar,并在程序Foo
中创建一个类加载器,只是为了加载Bar
类。
它看起来像这样:
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();
}
}
}
更新:
带反射(无需接口(:
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();
}
}
}