JAR im JAR und der URLClassloader

Status
Nicht offen für weitere Antworten.

DocMcFly

Bekanntes Mitglied
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:
Code:
\
\META-INF\config.xml
\classes\RunMe.class
\libs\driver.jar

driver.jar:
Code:
\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

Top Contributor
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

Bekanntes Mitglied
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

Top Contributor
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

Bekanntes Mitglied
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
 

DocMcFly

Bekanntes Mitglied
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

Bekanntes Mitglied
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
 

DocMcFly

Bekanntes Mitglied
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:
Code:
    // 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();


Code:
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;
    }
  }
}
 
T

tutnixzursache

Gast

DocMcFly

Bekanntes Mitglied
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
 
T

tutnixzursache

Gast
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]
 
S

SatisfiedUser

Gast
Hi,

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

Thx
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben