从另一个jar中删除jar

时间:2017-07-06 07:53:47

标签: java jar

我有两个jar文件,Foo.jarBar.jar。在Foo.jar内,我有一个Foo课,其main方法如下:

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以关闭其ClassLoader:

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中的ClassLoader会导致Foo.jar的某些事情变得混乱,但我还没有找到任何其他方法可以让我从{{1}删除Bar.jar }}

此示例的一般结构非常类似于实际应用程序中发生的情况,后者稍后使用Foo.jar。我尝试过创建一些其他类而不是java.nio.file.SimpleFileVisitor,但没有一个崩溃,但我想这取决于它们是否在java.no.file.SimpleFileVisitor调用Foo之前由ClassLoader加载

更新

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

所以现在的设置是:

  1. 我在Bar中使用Foo方法和main方法使用了课程speak()
  2. Foo.jar中使用简单Bar方法的课程sayHello()
  3. Bar.jar函数中,我使用自定义类加载器创建main的实例foo并调用Foo
  4. foo.speak()我创建foo.speak() bar的实例,如下所示:Bar&lt; - 请注意,这里没有任何反映或任何幻想。然后我拨打Bar bar = new Bar()
  5. bar.sayHello()我致电bar.sayHello()
  6. 然后返回this.getClass().getClassLoader().close(),删除main
  7. 然后是代码:

    Foo.java(在Foo.jar中)

    Bar.jar

    Bar.java(在Bar.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();
      }
    }
    

    CustomClassLoader(在Foo.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();
        }
      }
    }
    

    输出

    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.jarCustomClassLoader可以成功从Bar.jar中删除调用Foo之后{1}}。

    现在,这解决了我的问题。但是,我不确定我是否正确解决了bar.sayHello()。事实上,CustomClassLoader有效地做的唯一事情就是公开CustomClassLoader。我尝试了很多不同的方法来实现findClass(),但却无法让它工作,只是偶然我尝试了loadClass()而且它有效。

    有没有人对如何使用findClass()有任何意见?

1 个答案:

答案 0 :(得分:0)

好的,所以我会这样做:

我会定义一个Mac OS X 10.12.5 Chrome Browser 59.0.3071.115 npm 5.0.4 cucumber@2.3.1 nightwatch@0.9.16 nightwatch-cucumber@7.1.10 v8.0.0 使用的界面,并由Foo实施:

Bar

接口必须位于Foo.jar。

public interface BarIntf { public void sayHello(); } 课程将实施Bar

BarIntf

现在,我没有将Foo.jar和Bar.jar放在类路径中,而是只放入Foo.jar,并在程序public class Bar implements BarIntf { @Override public void sayHello() { System.out.println("Hello! I'm Bar!"); } } 中创建一个类加载器,只是为了加载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();
        }
    }
}