从另一个罐中删除一个罐子



我有两个jar文件,Foo.jarBar.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之前被类加载器加载


更新

在弄乱了类加载器(我仍然不完全理解(之后,我有一些东西似乎足够了。但是,我担心我正在做一些有点狡猾的事情,并且有一种更"正确"的方式来处理我正在做的类加载器操作。

所以现在的设置是:

  1. 我在Foo.jar中有一个main方法和一个speak()方法的类Foo
  2. Bar.jar中使用简单sayHello()方法Bar类。
  3. main函数中,我使用自定义类装入器创建了一个Foofoo实例并调用foo.speak()
  4. foo.speak()我创建了一个实例barBar如下:Bar bar = new Bar()<-请注意,这里没有反射或任何花哨的东西。然后我打电话给bar.sayHello()
  5. bar.sayHello()我称之为this.getClass().getClassLoader().close()
  6. 然后,所有内容都返回到删除Bar.jarmain

然后代码:

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();
}
}
}

最新更新