# JAR im JAR und der URLClassloader



## DocMcFly (4. Feb 2008)

Hallo,

Ich bin dabei ein eigenes PlugIn-System zu backen. Dazu übergebe ich ein JAR-File den URLClasslaoder. Die JAR-Datei hat ca. folgenden Aufbau:

plugin.jar:

```
\
\META-INF\config.xml
\classes\RunMe.class
\libs\driver.jar
```

driver.jar:

```
\driver\Test.class
```

In der config.xml steht u.a. drin, welche Klasse gestartet werden soll (zB RunMe.class) <- Das funktioniert!

Wenn aber die "RunMe" Klasse aus einer anderem JAR etwas benötigt (zB "driver.Test"), so soll man angeben können, welche JARs (zB \libs\driver.jar) in den PlugIn-ClassPath hinzugefügt werden sollen. <-- so wars geplant...

Um das hin zu bekommen hab ich folgendes versucht: 

1. Ich hab den URLClassloader abgeleitet und so die Methode "addURL()" auf public geschaltet. 
2. Dem URLClassloader hab ich "file:/plugin.jar" geben. (funktioniert!)
3. Lese aus der "\META-INF\config.xml" "\libs\driver.jar" aus. (funktioniert!)
4. Mache aus "\libs\driver.jar" -> "jar:file:/plugin.jar!libs\driver.jar" (keine Exception!)
5. Suche die Klasse "driver.Test" aus der driver.jar. <--- Aber dann Fehlanzeige!  :?: 

Wo liegt mein Denkfehler? 

Kann man "addURL()" nicht einfach nachträglich verwenden? (oder vielleicht nur im Konstruktor?)

"jar:file:/plugin.jar!libs\driver.jar" <-- interpretiert der Classloader das so wie ich das denke: Da ist eine JAR in einer JAR und die innere JAR soll zu Classpath hinzugefügt werden... ?

naja .. danke fürs Lesen und für hilfreiche Antworten...

Clemens


----------



## Wildcard (4. Feb 2008)

jar in jar funktioniert nicht.
Warum willst du überhaupt ein eigenes Plugin Framework aufbauen?
Man sieht doch am Beispiel Eclipse das sowas nicht trivial ist.
Verwende lieber etwas bestehendes.


----------



## DocMcFly (5. Feb 2008)

Ist eigentlich ganz einfach, weil ich ein PlugIn-System bereits habe, welches gut funktioniert. 

Bisher waren die PlugIns aber äußerst einfach... Sie haben keine weiteren JAR benötigt. Die PlugIn haben sich mit dem begnügt, was die Anwendung anbot oder was sie in eigenen Klassen dabei hatten. 

Aber mehr Anforderungen auch gleich mehr Herausforderungen... 

Wenn Du aber sagst JAR in 'ner JAR geht nicht - dann meine Frage: Ist das ein Rechte-Problem (SecurityManager) oder liegt es an der "Einfachheit" des URLClassloaders? 

Danke Gruß Clemens


----------



## Wildcard (5. Feb 2008)

Es liegt daran, das kein Classloader darauf ausgelegt ist.
Wenn du dir Eclipse zum Beispiel ansiehst, dann werden PlugIns die jars enthalten in configuration\org.eclipse.osgi\bundles entpackt.


----------



## DocMcFly (5. Feb 2008)

mmh... das ist aber schade... Ich fände besonders schick, wenn man die JARs die für ein PlugIn benötigt werden un angetastet läßt und nicht erst in ein temp-Verzeichnis packt...  Solche Dateioperationen machen die Sache "gefühlt" empfindlich und unsauber. 

Ist es schwer so einen Classloader zu bauen? Existierten Klassen die aus ByteCode ne Classe machen? 

Gruß Clemens


----------



## Wildcard (5. Feb 2008)

Du solltest dich mit OSGi beschäftigen.


> Existierten Klassen die aus ByteCode ne Classe machen


?
Bytecode steht doch bereits in Klassen.


----------



## DocMcFly (5. Feb 2008)

mmh... 

Okay... meine letzte Frage hat sich schon erledigt... Laut der API: 





> *protected final Class defineClass(byte[] b, int off, int len)*
> Erzeugt anhand der im Byte-Array b ab der Position off stehenden len Bytes eine Klassendefinition und liefert diese in einem Class-Objekt zurück. Dieses Objekt muss anschließend an resolveClass() übergeben werden, um die Klasse benutzen zu können.


Aber damit löst sich immer noch nicht mein Problem...  Ich hab ein wenig in den Code reingesehen (so weit die Sourcen von Sun mit geliefert werden...) 

Wenn ich das richtig beurteile: Das erste Problem ist schon im URL-Objekt. Geschachtelte Protokolle funktionieren nicht. Also zum Beispiel: jar:file:/test.jar!driver.jar werden nicht richtig aufgelöst. 

Also müsste ich erstmal eine URL-Ableitung bauen die geschachtelte URL hinbekommt. 

Dann melde ich mich wieder ...

Gruß Clemens


----------



## DocMcFly (5. Feb 2008)

so ein Dreck ... URL ist *final*!

Auspacken ist so unelegant... Schreibrechte, Pakete wieder löschen nach dem nutzen und was wenn diese noch gesperrt sind? und wie sieht es aus, wenn des Programm mal abstürzt, dann muss man wieder aufräumen... 

mmmh... Sun Du enttäuschst mich... :noe: 

Clemens


----------



## Wildcard (5. Feb 2008)

DocMcFly hat gesagt.:
			
		

> mmmh... Sun Du enttäuschst mich... :noe:


OSGi?


----------



## DocMcFly (6. Feb 2008)

> OSGi?


Da hab ich nur die OSGi Alliance gefunden... aber keinen verwertbaren Code... Vielleicht bin ich einfach zu doof zum Suchen. 

ABER EGAL!!!  :lol: 

Darf ich präsentieren: Mein PlugInClassloader - der die wichtigesten Aufgaben für meine Anwendungung erfüllt. 
(*getResouce(); getResourceAsStream(); loadClass();* )

Mit der Zeit wird dieser ClassLoader wahrscheinlich noch erweitert...

Kurze Funktionsbeschreibung: 

```
// Wo ist das PlugIn?
    File f = new File("/plugIn.jar");

    // Classloader mit PlugIn versorgen...
    PlugInClassLoader pcl = new PlugInClassLoader(f.toURL(), this.getClass().getClassLoader());

    // Die eingenisteten JARs angeben...
    pcl.addNestedJarPath("libs/JConsole.jar");
   
    // und tata man kann die Klasse erzeugen... hehe!
    Class c = pcl.loadClass("net.cylancer.lib.swing.JConsolePanel");
    Object _object = c.newInstance();
```



```
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import java.io.InputStream;

import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class PlugInClassLoader extends URLClassLoader {
  List<String> nestedJarPath = new ArrayList<String>();
  Map<String, Manifest> manifests = new HashMap<String, Manifest>();
  URL plugInURL = null;

  public PlugInClassLoader(URL _url) {
    super(new URL[] { _url });
    this.plugInURL = _url;
  }

  public PlugInClassLoader(URL _url, ClassLoader parent) {
    super(new URL[] { _url }, parent);
    this.plugInURL = _url;
  }

  public void addNestedJarPath(String _nestedJarPath) {
    if (super.getResource(_nestedJarPath) != null) {
      this.nestedJarPath.add(_nestedJarPath);
      Manifest _manifest = null;
      try {
        _manifest = new Manifest(this.getResourceAsStream("META-INF/MANIFEST.MF"));

      } catch (IOException e) {
        _manifest = new Manifest();
      }
      this.manifests.put(_nestedJarPath, _manifest);
    } else
      throw new RuntimeException("Nested jar-file not found!");
  }

  public void removeNestedJarPath(String _nestedJarPath) {
    if (this.nestedJarPath.contains(_nestedJarPath)) {
      this.nestedJarPath.remove(_nestedJarPath);
      this.manifests.remove(_nestedJarPath);
    } else
      throw new RuntimeException("Nested jar-file not found!");
  }


  public URL findResource(final String name) {
    URL _return = super.findResource(name);

    if (_return == null) {
      for (int i = 0, _size = this.nestedJarPath.size(); i < _size; i++) {
        JarURLConnection _connection;
        try {
          JarURLConnection _jarURLConnection = 
            (JarURLConnection)getResource(this.nestedJarPath.get(i)).openConnection();
          JarFile _jarFile = _jarURLConnection.getJarFile();
          ZipInputStream _zipInputStream = 
            new ZipInputStream(_jarFile.getInputStream(_jarFile.getEntry(this.nestedJarPath.get(i))));
          ZipEntry _zipEntry = null;
          while ((_zipEntry = _zipInputStream.getNextEntry()) != null)
            if (_zipEntry.getName().equals(name))
              return new URL("jar:jar:" + this.plugInURL + "!/" + this.nestedJarPath.get(i) + "!/" + name);

        } catch (IOException e) {
          return null;
        }
      }
      return null;
    } else
      return _return;
  }


  protected Class<?> _findClass(String name) throws ClassNotFoundException {
    URL _url = getResource(name.replaceAll("\\.", "/").concat(".class"));
    String _nestedJarPath = getNestedJarPath(_url);
    if (_nestedJarPath != null && this.nestedJarPath.contains(_nestedJarPath)) {

      // Package anlegen bzw. prüfen...
      int _index = name.lastIndexOf('.');
      if (_index != -1) {
        String _pkgname = name.substring(0, _index);
        Package _package = this.getPackage(_pkgname);
        if (_package == null) {
          URL _packageUrl = getResource(_pkgname.replaceAll("\\.", "/").concat("/"));
          Manifest _manifest = this.manifests.get(_nestedJarPath);
          if (_manifest != null) {
            definePackage(_pkgname, _manifest, _packageUrl);
          } else {
            definePackage(_pkgname, null, null, null, null, null, null, null);
          }
        }
      }
      // Klasse anlegen...
      InputStream _byteCodeStream = this.getResourceAsStream(name.replaceAll("\\.", "/").concat(".class"));
      byte[] data = new byte[1024];
      int read = 0;
      ByteArrayOutputStream _byteArrayOutputStream = new ByteArrayOutputStream();
      try {
        while ((read = _byteCodeStream.read(data, 0, 1024)) != -1) {
          _byteArrayOutputStream.write(data, 0, read);
        }
      } catch (IOException e) {
        throw new ClassNotFoundException(name);
      }
      byte[] _byteCode = _byteArrayOutputStream.toByteArray();

      Class _class = defineClass(name, _byteCode, 0, _byteCode.length);
      this.resolveClass(_class);
      return _class;
    } else
      throw new ClassNotFoundException(name);
  }


  protected Class<?> findClass(String name) throws ClassNotFoundException {
    try {
      return super.findClass(name);
    } catch (ClassNotFoundException e) {
      try {
        return this._findClass(name);
      } catch (ClassNotFoundException ce) {
        throw new ClassNotFoundException(name);
      }

    }
  }

  private String getNestedJarPath(URL _url) {
    if (_url == null)
      return null;
    else if ("jar".equals(_url.getProtocol()) && _url.getFile().startsWith("jar:")) {
      int _indexStartNestedJar = _url.toString().indexOf("!");
      int _indexEndNestedJar = _url.toString().lastIndexOf("!");
      return _url.toString().substring(_indexStartNestedJar + 2, _indexEndNestedJar);
    } else
      return null;
  }

  public InputStream getResourceAsStream(String name) {
    URL _url = getResource(name);
    try {
      String _nestedJarPath = getNestedJarPath(_url);
      if (_nestedJarPath != null) {
        JarURLConnection _jarURLConnection = (JarURLConnection)getResource(_nestedJarPath).openConnection();
        JarFile _jarFile = _jarURLConnection.getJarFile();
        ZipInputStream _zipInputStream = 
          new ZipInputStream(_jarFile.getInputStream(_jarFile.getEntry(_nestedJarPath)));
        ZipEntry _zipEntry = null;
        while ((_zipEntry = _zipInputStream.getNextEntry()) != null)
          if (_zipEntry.getName().equals(name))
            return new BufferedInputStream(_zipInputStream);
        return null;
      } else
        return _url != null ? _url.openStream() : null;
    } catch (IOException e) {
      return null;
    }
  }
}
```


----------



## tutnixzursache (7. Feb 2008)

Ich dachte bisher auch immer das man jars nicht Schachteln kann, bis ich vor kurzem die Insel mal wieder nach neuen Sachen durchgesehen hab, da bin ich auf das hier gestoßen:
www.rz.uni-hohenheim.de/anw/programme/prg/java/tutorials/javainsel4/javainsel_25_002.htm#Rxx365java25002040009D31F0231D9
Ich habe es allerdings noch nicht ausprobiert, sag bitte bescheid ob das so klappt.


Eine OSGi Implementierung gibt es auch von Apache:
felix.apache.org/site/index.html

Gruß tutnixzursache


----------



## DocMcFly (8. Feb 2008)

Hallo,

Ich habe das mit dem ClassPath: in der Manifest-Datei schon ausprobiert. Es funktioniert bei mir nicht. 
Dann bin ich auf folgendes gestossen: 

http://java.sun.com/docs/books/tutorial/deployment/jar/downman.html

Da steht, das der ClassPath nicht auf JARs in JARs verweist. :-( Da scheinen die Insel-Autoren nicht perfekt recherchiert zu haben!

Das mit dem OSGi framework erscheint mir sehr abstrakt... zumal ich ja bereits ein bestehendes Anwendungsframework habe, welches ohne Probleme mit PlugIns umgehen kann. Nur das mit dem geschachtelten Import wollte nicht so recht! :-(

Naja - der gepostete ClassLoader funktioniert.... ich werde diesen noch etwas beschleunigen in dem ich die JARs beim Import indiziere. 

Vielleicht poste ich dann die Nächste Version wieder hier und CodeSchnippsel.

Trotzdem Danke... 
Gruß Clemens


----------



## tutnixzursache (8. Feb 2008)

Naja OSGi ist sehr mächtig und wird auch mittlerweile von vielen Unternehmen unterstützt.

Bisher habe ich immer das auspacken von libs immer mit Ant automatisiert

[/code]


----------



## DocMcFly (8. Feb 2008)

Habe eine bessere Variante mit Indizierung und Kommentierung in der CodeSchnipsel-Abteilung abgelegt. 

Siehe: http://www.java-forum.org/de/topic63851_classloader-plugins-pluginclassloader.html

Gruß Clemens


----------



## SatisfiedUser (27. Sep 2011)

Hi,

DocMacFly.....wollte mich nur bedanken für deine Veröffentlichung.
Das war genau das was ich suchte und es funktioniert super.

Thx


----------

