Plugin-System



  • Hallo Leute!

    Ich will fuer meinen kleinen Server ein Plugin-System erstellen, so dass man jedes x-beliebige Protokoll mit dem Server verwenden kann.

    Jetzt stellen sich mir 2 Fragen:
    a) wie ladet man *.class Dateien dynamisch?
    Mittels Class.forName() und .newInstance() kann ich ja eine Klasse dynamisch laden, doch was wenn ich alle Klassen laden will, die im Unterverzeichnis 'plugin' liegen?

    Mein Versuch mit

    Class cl=Class.forName("nix.foo.test.foo");
                Runnable r=(Runnable)cl.newInstance();
                r.run();
    

    hat nicht funktioniert.

    Wobei die Klasse 'foo' im Unterverzeichnis 'nix' in der Datei 'foo.class' im package 'test' zu finden ist.
    (ich hab auch noch andere Kombinationen ausprobiert) 😞

    b) Sollte ich die Plugins einmal beim starten alle einlesen (in eine Hashmap oder so) oder lieber bei jeder Anfrage an den Server das Plugin Verzeichnis durchsuchen?
    Mir gefaellt es nicht, dass man den Server neustarten muss, wenn man eine Plugin hinzufuegt.
    Allerdings ist die 2. Variante wirklich lahm, und faellt deshalb weg.

    Gibt es ein Mittelding?

    Soll ich bei jeder Anfrage eine 'Config Datei' laden - in der man Plugins registrieren muss? Diese Config Datei kann man dann ja auch cachen, und es sollte recht schnell sein. Aber man muesste dann halt jedes Plugin haendisch dort eintragen...

    Was denkt ihr, ist das beste?


  • Mod

    Ich habe mich bei meinem Projekt dafür entschieden, alle Klassen beim Start in eine geeignete Datenstruktur, einen gerichteten Graphen, zu stecken. Das macht bei mir Sinn, weil mein Projekt nahezu nur aus "Plugins" besteht. Sicherlich ist das nicht allgemein eine gute Lösung, die ideale Lösung wird sich von Fall zu Fall unterscheiden.

    Hier ist zumindest mal die wichtigste Klasse, die ich für diesen Vorgang brauche. Vielleicht hilft sie dir ja auch etwas weiter, oder du kriegst zumindest eine Anregung, wie du es machen könntest.

    package controller;
    
    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.Modifier;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.net.URL;
    import java.util.Enumeration;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.jar.JarEntry;
    import java.util.jar.JarFile;
    
    import container.DirectedGraph;
    
    public class DynamicClassLoader
    {
       private static DynamicClassLoader instance;
    
       private DirectedGraph classGraph;
    
       private DynamicClassLoader ()
       {
          classGraph = new DirectedGraph();
          loadAllClasses();
       }
    
       public static DynamicClassLoader getInstance()
       {
          if (instance == null) instance = new DynamicClassLoader();
          return instance;
       }
    
       public void loadAllClasses ()
       {
          URL controllerURL = ClassLoader.getSystemResource("controller");
          String controllerURLString = controllerURL.toString();
          try
          {
             if (controllerURLString.startsWith("jar:"))
             {
                int jarEnd = controllerURLString.indexOf("!");
                String jarString = controllerURLString.substring(4,jarEnd);
                JarFile jarFile;
                jarFile = new JarFile(new File(new URI(jarString)));
                loadClasses (jarFile);
             }
             else
             {
                File controllerDirectory = new File (new URI(controllerURLString));
                File baseDirectory = controllerDirectory.getParentFile();
                loadClasses (baseDirectory);
             }
          }
          catch (IOException e)
          {
          }
          catch (URISyntaxException e)
          {
          }
       }
    
       public void loadClasses (File baseDirectory)
       {
          LinkedList fileList = new LinkedList();
          getClassFiles (fileList, baseDirectory);
          LinkedList classFileList = new LinkedList();
          final int start = baseDirectory.getAbsolutePath().length() + 1;
          Iterator iterator = fileList.iterator();
          while (iterator.hasNext())
          {
             classFileList.add(((String)iterator.next()).substring(start));
          }
          loadClasses(classFileList);
       }
    
       private void getClassFiles (LinkedList classFileList, File directory)
       {
          File [] files = directory.listFiles();
          for (int i = 0 ; i < files.length ; ++i)
          {
             String name = files[i].getAbsolutePath ();
             if (name.endsWith(".class"))
             {
                classFileList.add(name);
             }
             else if (files[i].isDirectory())
             {
                getClassFiles (classFileList,files[i]);
             }
          }
       }
    
       public void loadClasses (JarFile jarFile)
       {
          Enumeration jarEntries = jarFile.entries ();
          LinkedList fileList = new LinkedList();
          while (jarEntries.hasMoreElements())
          {
             JarEntry entry = (JarEntry)jarEntries.nextElement();
             String name = entry.getName();
             if (name.endsWith(".class"))
             {
                fileList.add(name);
             }
          }
          loadClasses(fileList);
       }
    
       private void loadClasses (LinkedList classFileList)
       {
          final Iterator iterator = classFileList.iterator();
          final char separator = File.separatorChar;
          while (iterator.hasNext())
          {
             String name = (String)iterator.next();
             name = name.substring (0,name.length() - 6);
             name = name.replace('/','.');
             name = name.replace(separator,'.');
             try
             {
                final Class currentClass = Class.forName(name);
                addToClassGraph(currentClass);
             }
             catch (ClassNotFoundException e)
             {
             }
             catch (SecurityException e)
             {
             }
          }
       }
    
       private void addToClassGraph(Class value)
       {
          Class superClass = value.getSuperclass();
          if (superClass != null) classGraph.addEdge(superClass,value);
          Class[] interfaces = value.getInterfaces();
          for (int i = 0 ; i < interfaces.length ; ++i)
          {
             classGraph.addEdge(interfaces[i],value);
          }
       }
    
       public LinkedList listDerivedClasses (Class superClass)
       {
          LinkedList derivedClassesList = classGraph.listAllSuccessors(superClass);
          Iterator iter = derivedClassesList.iterator();
          while (iter.hasNext())
          {
             Class currentClass = (Class)iter.next();
             int modifiers = currentClass.getModifiers();
             if (Modifier.isAbstract(modifiers) ||
                 Modifier.isInterface(modifiers) ||
                 !Modifier.isPublic(modifiers))
             {
                iter.remove();
             }
          }
          return derivedClassesList;
       }
    }
    

    Wie du vielleicht gesehen hast, nutze ich ein paar schmutzige Tricks. Mir ist da noch kein besserer Weg eingefallen. Weiterhin könnten einige überflüssige Dinge in der Klasse stehen, weil ich die Klasse erst vor kurzem über einen längeren Zeitraum hinweg umgeschrieben habe. Aus dem gleichen Grund könnte die Klasse auch etwas buggy sein, mir ist aber noch nichts aufgefallen, in meiner "Standardnutzung" funktioniert sie zumindest.

    Nutzung der Klasse:

    Zuerst holt man sich mit getInstance die Instanz der Klasse. Die Klasse geht dann automatisch alle Dateien des Projekts durch und steckt alle Klassen in einen gerichteten Graphen. Wenn man schließlich die von einer Klasse abgeleiteten, instanziierbaren Klassen haben möchte, dann holt man sich diese mit der Methode listDerivedClasses.



  • Danke.

    Das ist n bisschen sehr komplex...

    Was ich nicht verstehe ist: wenn die .class Datei im selber Verzeichnis wie die Hauptdatei ist, dann kann ich mittels
    Class.forName("test.foo");
    die Klasse laden.

    Aber wenn die Datei in einem Unterverzeichnis ist, dann geht es nichtmehr:
    Class.forName("verzeichnis.test.foo");

    wie muss es richtig heissen.

    An deine source werde ich mich mal ransetzen! danke!



  • Das hat etwas mit dem Klassenpfad zu tun. Du müsstest dir wahrscheinlich einen eigenen ClassLoader schreiben (FileClassLoader) was aber nicht sonderlich schwer ist.



  • Ah, THX!
    👍

    Mit einem FileClassLoader geht es.

    Funktionieren tut es. Waere aber trotzdem nett, wenn ihr euch den FileClassLoader mal kurz ansehen koenntet. Passt der so?

    import java.io.*;
    
    public class FileClassLoader extends ClassLoader
    {
        private String dir;
    
        public FileClassLoader(String directory)
        {
            dir = directory;
        }
    
        protected Class findClass(String name) throws ClassNotFoundException
        {
            File file = new File(dir + File.separator + name + ".class");
    
            long size = file.length();
            if(size==0 || size>Integer.MAX_VALUE)
            {
                throw new ClassNotFoundException(dir + File.separator + name + ".class");
            }
            byte[] content = new byte[(int)size];
    
            try
            {
                FileInputStream in = new FileInputStream(file);
                in.read(content);
                in.close();
            }
            catch (Exception e)
            {
                throw new ClassNotFoundException(e.getMessage());
            }
            return defineClass(name, content, 0, content.length);
        }
    }
    

Anmelden zum Antworten