2017-02-19 3 views
1

Je tente de charger une classe à partir de JAR représenté sous la forme d'un tableau byte [] au moment de l'exécution.
Je connais deux choses sur la classe à la charge:

1. Il met en oeuvre « RequiredInterface »
2. Je sais que c'est un nom qualifié: « sample.TestJarLoadingClass »

j'ai trouvé le solution dans lequel je dois étendre ClassLoader mais il jette:ClassNotFoundException lors de la tentative de chargement de la classe à partir du fichier JAR externe au moment de l'exécution

Exception in thread "main" java.lang.ClassNotFoundException: sample.TestJarLoadingClass 
    at java.lang.ClassLoader.findClass(ClassLoader.java:530) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357) 
    at java.lang.Class.forName0(Native Method) 
    at java.lang.Class.forName(Class.java:348) 
    at tasks.Main.main(Main.java:12) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:498) 
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) 

chaque fois que je veux charger la classe.

Quelle peut être la raison de cette situation et comment puis-je m'en débarrasser?
Toute aide très appréciée

méthode principale:

public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { 
    Path path = Paths.get("src/main/java/tasks/sample.jar"); 
    RequiredInterface requiredInterface = (RequiredInterface) Class.forName("sample.TestJarLoadingClass", true, new ByteClassLoader(Files.readAllBytes(path))).newInstance(); 
} 

classe personnalisée chargeur:

import java.io.ByteArrayInputStream; 
    import java.io.IOException; 
    import java.io.InputStream; 
    import java.util.Collections; 
    import java.util.HashSet; 
    import java.util.Set; 
    import java.util.zip.ZipEntry; 
    import java.util.zip.ZipInputStream; 

    public class ByteClassLoader extends ClassLoader { 
     private final byte[] jarBytes; 
     private final Set<String> names; 

     public ByteClassLoader(byte[] jarBytes) throws IOException { 
      this.jarBytes = jarBytes; 
      this.names = loadNames(jarBytes); 
     } 

     private Set<String> loadNames(byte[] jarBytes) throws IOException { 
      Set<String> set = new HashSet<>(); 
      try (ZipInputStream jis = new ZipInputStream(new ByteArrayInputStream(jarBytes))) { 
       ZipEntry entry; 
       while ((entry = jis.getNextEntry()) != null) { 
        set.add(entry.getName()); 
       } 
      } 
      return Collections.unmodifiableSet(set); 
     } 

     @Override 
     public InputStream getResourceAsStream(String resourceName) { 
      if (!names.contains(resourceName)) { 
       return null; 
      } 
      boolean found = false; 
      ZipInputStream zipInputStream = null; 
      try { 
       zipInputStream = new ZipInputStream(new ByteArrayInputStream(jarBytes)); 
       ZipEntry entry; 
       while ((entry = zipInputStream.getNextEntry()) != null) { 
        if (entry.getName().equals(resourceName)) { 
         found = true; 
         return zipInputStream; 
        } 
       } 
      } catch (IOException e) {; 
       e.printStackTrace(); 
      } finally { 
       if (zipInputStream != null && !found) { 
        try { 
         zipInputStream.close(); 
        } catch (IOException e) {; 
         e.printStackTrace(); 
        } 
       } 
      } 
      return null; 
     } 
} 

RequiredInterface:

public interface RequiredInterface { 
    String method(); 
} 

classe dans le fichier JAR:

package sample; 
public class TestJarLoadingClass implements RequiredInterface { 
    @Override 
    public String method() { 
     return "works!"; 
    } 
} 
+0

J'ai utilisé le code que vous avez posté sur ma machine et tout a fonctionné. Pourriez-vous s'il vous plaît publier l'exception exacte que vous avez? Vérifiez également si le chemin que vous utilisez indique ** vraiment ** à jar contenant la classe que vous voulez charger. –

+0

Ok, j'ai posté tout le message d'exception. J'ai vérifié toutes les erreurs "stupides" possibles, tout devrait fonctionner. S'il vous plaît vérifiez si vous n'avez pas la classe nécessaire pour être chargé dans une gamme de votre chargeur de classe. –

+1

Oui, vous avez raison, par erreur, j'ai ajouté la dépendance à jar avec la classe au lieu de jar avec l'interface. Désolé –

Répondre

1

À mon avis, nous avons deux problèmes:

d'abord, vous devez remplacer findClass méthode qui contient une logique réelle de la classe de chargement. Le principal défi ici est de trouver une partie du tableau d'octets qui contient votre classe - puisque vous avez un pot entier en tant que tableau d'octets, vous devrez utiliser JarInputStream pour analyser votre tableau d'octets pour votre classe.

Mais cela pourrait ne pas être suffisant parce que votre RequiredInterface est inconnue pour votre ByteClassLoader - donc vous serez en mesure de lire lui-même classe, mais la définition de la classe contient des informations qu'il met en œuvre RequiredInterface qui est un problème pour votre chargeur de classe. Celui-ci est facile à corriger, vous avez juste besoin de passer le chargeur de classe régulière en tant que paramètre constructeur à votre et utiliser super(parentClassLoader).

Voici ma version:

public class ByteClassLoader extends ClassLoader { 
    private final byte[] jarBytes; 

    public ByteClassLoader(ClassLoader parent, byte[] jarBytes) throws IOException { 
     super(parent); 
     this.jarBytes = jarBytes; 
    } 

    @Override 
    protected Class<?> findClass(String name) throws ClassNotFoundException { 

     // read byte array with JarInputStream 
     try (JarInputStream jis = new JarInputStream(new ByteArrayInputStream(jarBytes))) { 
      JarEntry nextJarEntry; 

      // finding JarEntry will "move" JarInputStream at the begining of entry, so no need to create new input stream 
      while ((nextJarEntry = jis.getNextJarEntry()) != null) { 

       if (entryNameEqualsClassName(name, nextJarEntry)) { 

        // we need to know length of class to know how many bytes we should read 
        int classSize = (int) nextJarEntry.getSize(); 

        // our buffer for class bytes 
        byte[] nextClass = new byte[classSize]; 

        // actual reading 
        jis.read(nextClass, 0, classSize); 

        // create class from bytes 
        return defineClass(name, nextClass, 0, classSize, null); 
       } 
      } 
      throw new ClassNotFoundException(String.format("Cannot find %s class", name)); 
     } catch (IOException e) { 
      throw new ClassNotFoundException("Cannot read from jar input stream", e); 
     } 
    } 


    private boolean entryNameEqualsClassName(String name, JarEntry nextJarEntry) { 

     // removing .class suffix 
     String entryName = nextJarEntry.getName().split("\\.")[0]; 

     // "convert" fully qualified name into path 
     String className = name.replace(".", "/"); 

     return entryName.equals(className); 
    } 
} 

et l'utilisation

RequiredInterface requiredInterface = (RequiredInterface)Class.forName("com.sample.TestJarLoadingClass", true, new ByteClassLoader(ByteClassLoader.class.getClassLoader(), Files.readAllBytes(path))).newInstance(); 
    System.out.println(requiredInterface.method()); 

S'il vous plaît être conscient que ma mise en œuvre suppose que le nom du fichier = nom de la classe, donc pour les classes par exemple qui ne sont pas de haut niveau ne sera pas être trouvé. Et bien sûr, certains détails pourraient être plus polis (comme la gestion des exceptions).

+0

Je suis en train d'exécuter votre solution, mais chaque fois que je reçois pour nom « TestJarLoadingClass » exception: Exception dans le thread « principal » java.lang.NoClassDefFoundError: TestJarLoadingClass (nom incorrect: échantillon/TestJarLoadingClass) –

+0

@adsqew il est probablement parce que je l'ai testé sur 'TestJarLoadingClass' que j'ai mis dans le paquetage par défaut, donc mon exemple n'en a pas tenu compte. J'ai mis à jour l'exemple et maintenant il prend en considération différents paquets (jetez un oeil à new 'entryNameEqualsClassName'). Essayez également de placer le point d'arrêt au début de la méthode 'findClass' et regardez comment les instances JarEntry apparaîtront à chaque itération. Il vous donnera probablement plus de connaissances sur la façon dont les classes sont stockées en interne dans le fichier Jar –

+0

Très bien, maintenant il fonctionne comme prévu. Je voudrais vous demander à la fin si vous savez pourquoi nextJarEntry.getSize() donne le résultat -1 (c'est la valeur par défaut) à chaque fois? Je dois passer la taille .class manuellement. –