2008-10-15 3 views
121

ich so etwas wie dies wollen erweitern tun:zur Laufzeit alle Klassen in einer Java-Anwendung finden, die eine Basisklasse

List<Animal> animals = new ArrayList<Animal>(); 

for(Class c: list_of_all_classes_available_to_my_app()) 
    if (c is Animal) 
     animals.add(new c()); 

Also, ich in meiner Anwendung Universum an alle Klassen aussehen wollen, und Wenn ich ein Objekt finde, das von Animal stammt, möchte ich ein neues Objekt dieses Typs erstellen und es der Liste hinzufügen. Dadurch kann ich Funktionalität hinzufügen, ohne eine Liste von Dingen aktualisieren zu müssen. Ich kann die folgende vermeiden:

List<Animal> animals = new ArrayList<Animal>(); 
animals.add(new Dog()); 
animals.add(new Cat()); 
animals.add(new Donkey()); 
... 

Mit dem obigen Ansatz, kann ich einfach eine neue Klasse erstellen, das Tier erweitert und es wird automatisch abgeholt.

UPDATE: 2008.10.16 09.00 Uhr Pacific Standard Time:

Diese Frage viele große Antworten erzeugt hat - danke. Aus den Antworten und meinen Nachforschungen habe ich herausgefunden, dass das, was ich wirklich tun möchte, unter Java nicht möglich ist. Es gibt Ansätze, wie den ServiceLoader-Mechanismus von ddimitrov, die funktionieren können - aber sie sind sehr schwer für das, was ich will, und ich glaube, ich verschiebe das Problem einfach von Java-Code in eine externe Konfigurationsdatei.

Eine andere Möglichkeit zu sagen, was ich will: Eine statische Funktion in meiner Animal-Klasse findet und instanziiert alle Klassen, die von Animal erben - ohne weitere Konfiguration/Codierung. Wenn ich konfigurieren muss, kann ich sie auch einfach in der Tierklasse instantiieren. Ich verstehe das, weil ein Java-Programm nur eine lose Vereinigung von .class-Dateien ist, so ist es genau so.

Interessanterweise scheint dies fairly trivial in C# zu sein.

+1

Nach einer Weile suchen, so scheint es, dass dies eine schwierige Nuss in Java zu knacken. Hier ist ein Thread, der einige Informationen hat: http://forums.sun.com/thread.jspa?threadID=341935&start=15 Die Implementierungen in es sind mehr als ich brauche, ich denke, ich bleibe nur bei der zweite Implementierung für jetzt. – JohnnyLambada

+0

Setzen Sie das als Antwort und lassen Sie die Leser darüber abstimmen – VonC

+3

Der Link dazu in C# macht nichts Besonderes. Eine C# -Assembly entspricht der Java-JAR-Datei. Es ist eine Sammlung kompilierter Klassendateien (und Ressourcen). Der Link zeigt, wie die Klassen aus einer einzelnen Assembly abgerufen werden. Sie können es fast so einfach mit Java machen. Das Problem für Sie ist, dass Sie alle JAR-Dateien und echte lose Dateien (Verzeichnisse) durchsuchen müssen; Sie müssten dasselbe mit .NET machen (suchen Sie einen PFAD irgendeiner Art oder verschiedener Arten). –

Antwort

126

Ich benutze org.reflections:

Reflections reflections = new Reflections("com.mycompany");  
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class); 
+5

Danke für den Link über org.reflections. Ich dachte fälschlicherweise, dass dies so einfach wäre wie in der .NET-Welt und diese Antwort hat mir viel Zeit gespart. – akmad

+0

Vielen Dank für diesen hilfreichen Link zum tollen Paket "org.reflections"! Damit habe ich endlich eine praktikable und saubere Lösung für mein Problem gefunden. – Hartmut

+2

Gibt es eine Möglichkeit, alle Reflexionen zu erhalten, d. H. Ohne ein bestimmtes Paket zu notieren, aber alle verfügbaren Klassen zu laden? –

2

Java lädt dynamisch Klassen, sodass Ihr Universum von Klassen nur diejenigen sein würde, die bereits geladen wurden (und noch nicht entladen sind). Vielleicht können Sie etwas mit einem benutzerdefinierten Klassenlader tun, der die Obertypen jeder geladenen Klasse überprüfen könnte. Ich glaube nicht, dass es eine API gibt, um die Menge der geladenen Klassen abzufragen.

5

Leider ist dies nicht vollständig möglich, da der ClassLoader Ihnen nicht sagt, welche Klassen verfügbar sind. Sie können, erhalten jedoch ziemlich nah so etwas wie dies zu tun:

for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) { 
    if (classpathEntry.endsWith(".jar")) { 
     File jar = new File(classpathEntry); 

     JarInputStream is = new JarInputStream(new FileInputStream(jar)); 

     JarEntry entry; 
     while((entry = is.getNextJarEntry()) != null) { 
      if(entry.getName().endsWith(".class")) { 
       // Class.forName(entry.getName()) and check 
       // for implementation of the interface 
      } 
     } 
    } 
} 

Edit: johnstok korrekt ist (in den Kommentaren), dass dies funktioniert nur für Standalone-Java-Anwendungen, und nicht unter einem Anwendungsserver arbeiten .

+0

Dies funktioniert nicht gut, wenn Klassen auf andere Weise geladen werden, z. B. in einem Web- oder J2EE-Container. – johnstok

+0

Sie könnten wahrscheinlich einen besseren Job machen, sogar unter einem Container, wenn Sie die Pfade (oft 'URL []', aber nicht immer, so dass es nicht möglich ist) von der ClassLoader-Hierarchie abfragen. Oft müssen Sie jedoch eine Berechtigung haben, da Sie normalerweise einen 'SecurityManager' in der JVM geladen haben. –

+0

Arbeitete wie ein Charme für mich! Ich befürchtete, dass dies zu schwer und langsam wäre und alle Klassen im Klassenpfad durchgehen würde, aber ich beschränkte die Dateien auf diejenigen, die "-ejb-" oder andere erkennbare Dateinamen enthielten, und es ist immer noch 100 mal schneller als das Starten ein eingebetteter Glasfisch oder EJBContainer. In meiner speziellen Situation analysierte ich dann die Klassen nach "Stateless", "Stateful" und "Singleton" Annotationen, und wenn ich sie sah, fügte ich den JNDI Mapped Name meiner MockInitialContextFactory hinzu. Danke nochmal! – cs94njw

0

Dies ist ein schwieriges Problem und Sie müssen diese Informationen mit statischen Analysen herausfinden, die zur Laufzeit nicht leicht verfügbar sind. Ermitteln Sie den Klassenpfad Ihrer App und durchsuchen Sie die verfügbaren Klassen und lesen Sie die Bytecode-Informationen einer Klasse, von welcher Klasse sie erbt. Beachten Sie, dass eine Klasse Dog nicht direkt von Animal erben kann, sondern von Pet erbt, das wiederum von Animal erbt. Daher müssen Sie diese Hierarchie verfolgen.

2

Danke an alle, die diese Frage beantwortet haben.

Es scheint in der Tat eine harte Nuss zu knacken. Ich gab auf und schuf ein statisches Array und Getting in meiner Basisklasse.

Es scheint, dass Java nicht für die Selbstentdeckung eingerichtet ist, wie C# ist. Ich nehme an, das Problem ist, dass eine Java-App nur eine Sammlung von.class files irgendwo in einer verzeichnis/jar-datei, die laufzeit weiß nichts über eine klasse, bis sie referenziert ist. Zu dieser Zeit lädt der Loader es - was ich versuche zu tun ist, es zu entdecken, bevor ich es referenziere, was nicht möglich ist, ohne ins Dateisystem zu gehen und zu schauen.

Ich mag immer Code, der sich selbst entdecken kann, anstatt mir über sich selbst zu erzählen, aber leider funktioniert das auch.

Danke nochmal!

+1

Java ist für die Selbstfindung eingerichtet. Sie müssen nur ein bisschen mehr Arbeit erledigen. Siehe meine Antwort für Details. –

0

Eine Möglichkeit ist, die Klassen verwenden, um eine statische Initialisierer zu machen ... Ich glaube nicht, diese vererbt werden (es wird nicht funktionieren, wenn sie):

public class Dog extends Animal{ 

static 
{ 
    Animal a = new Dog(); 
    //add a to the List 
} 

Es Sie diese hinzufügen erfordert Code für alle beteiligten Klassen. Aber es vermeidet, irgendwo eine große hässliche Schleife zu haben und jede Klasse zu testen, die nach Kindern von Animal sucht.

+3

Das funktioniert in meinem Fall nicht. Da die Klasse nirgendwo referenziert wird, wird sie nie geladen, so dass der statische Initialisierer niemals aufgerufen wird. Es scheint, dass ein statischer Initialisierer vor allem anderen aufgerufen wird, wenn die Klasse geladen wird - aber in meinem Fall wird die Klasse niemals geladen. – JohnnyLambada

7

Denke darüber aus einer Aspekt-Perspektive; Was Sie wirklich tun wollen, sind alle Klassen zur Laufzeit, die die Tierklasse erweitert haben. (Ich denke, das ist eine etwas genauere Beschreibung Ihres Problems als Ihr Titel; andernfalls glaube ich nicht, dass Sie eine Laufzeitfrage haben.)

Also was ich denke, Sie wollen einen Konstruktor Ihrer Basisklasse erstellen (Animal), die zu Ihrem statischen Array (ich bevorzuge ArrayLists, selbst, aber zu jedem) den Typ der aktuellen Klasse hinzufügt, die instanziiert wird.

Also, grob;

public abstract class Animal 
    { 
    private static ArrayList<Class> instantiatedDerivedTypes; 
    public Animal() { 
     Class derivedClass = this.getClass(); 
     if (!instantiatedDerivedClass.contains(derivedClass)) { 
      instantiatedDerivedClass.Add(derivedClass); 
     } 
    } 

Natürlich, werden Sie einen statischen Konstruktor für Tier müssen instantiatedDerivedClass initialisieren ... Ich denke, das werde tun, was Sie wollen wahrscheinlich. Beachten Sie, dass dies vom Ausführungspfad abhängig ist. Wenn Sie eine Dog-Klasse haben, die von Animal abgeleitet ist, die nie aufgerufen wird, haben Sie sie nicht in Ihrer Animal-Class-Liste.

+3

Keine Verbesserung des bestehenden Codes von OP. –

32

Der Java-Weg zu tun, was Sie wollen, ist die ServiceLoader Mechanismus.

Auch viele Leute rollen ihre eigenen, indem Sie eine Datei an einem bekannten Klassenpfad Speicherort (d. H. /META-INF/services/myplugin.properties) und dann ClassLoader.getResources() verwenden, um alle Dateien mit diesem Namen aus allen Gläsern aufzuzählen. Dies ermöglicht jedem Glas, seine eigenen Anbieter zu exportieren, und Sie können sie durch Reflexion mit Class.forName()

+1

Zum einfachen Generieren der META-INF-Servicedatei anhand von Anmerkungen zu den Klassen können Sie Folgendes überprüfen: http://metainf-services.kohsuke.org/ oder https://code.google.com/p/spi/ – elek

+0

Bleah . Fügt nur ein gewisses Maß an Komplexität hinzu, beseitigt aber nicht die Notwendigkeit, sowohl eine Klasse zu erstellen als auch sie in einem weit entfernten Land zu registrieren. –

+0

Bitte beachten Sie die Antwort unten mit 26 upvotes, viel besser – SobiborTreblinka

0

instanziieren. Ich löste dieses Problem ziemlich elegant mit Package Level Annotations und dann diese Annotation haben als Argument eine Liste von Klassen.

Find Java classes implementing an interface

Implementationen müssen nur eine package-info.java erstellen und legen Sie die Magie Anmerkung in der Liste der Klassen, die sie unterstützen wollen.

2

könnten Sie verwenden ResolverUtil (raw source) von der Stripes Framework
, wenn Sie etwas einfach und schnell benötigen, ohne dass bestehende Code Refactoring.

Hier ist ein einfaches Beispiel keine der Klassen geladen haben:

package test; 

import java.util.Set; 
import net.sourceforge.stripes.util.ResolverUtil; 

public class BaseClassTest { 
    public static void main(String[] args) throws Exception { 
     ResolverUtil<Animal> resolver = new ResolverUtil<Animal>(); 
     resolver.findImplementations(Animal.class, "test"); 
     Set<Class<? extends Animal>> classes = resolver.getClasses(); 

     for (Class<? extends Animal> clazz : classes) { 
      System.out.println(clazz); 
     } 
    } 
} 

class Animal {} 
class Dog extends Animal {} 
class Cat extends Animal {} 
class Donkey extends Animal {} 

in einem Anwendungsserver als auch seit Dies funktioniert auch wurde das ist, wo sie arbeiten entworfen;)

Der Code im Grunde tut die folgenden:

  • Iterierte über alle Ressourcen im Paket (en), die Sie angeben
  • halten nur th e Ressourcen in .class-
  • laden endet diese Klassen mit ClassLoader#loadClass(String fullyQualifiedName)
  • Prüft, ob Animal.class.isAssignableFrom(loadedClass);
1

OpenPojo Verwenden Sie Folgendes tun können:

String package = "com.mycompany"; 
List<Animal> animals = new ArrayList<Animal>(); 

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) { 
    animals.add((Animal) InstanceFactory.getInstance(pojoClass)); 
} 
Verwandte Themen