Kann ich es mit Reflexion oder so machen?
Kann ich es mit Reflexion oder so machen?
Antworten:
Ich habe eine Weile gesucht und es scheint verschiedene Ansätze zu geben, hier eine Zusammenfassung:
Die Reflexionsbibliothek ist sehr beliebt, wenn es Ihnen nichts ausmacht, die Abhängigkeit hinzuzufügen. Es würde so aussehen:
Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
ServiceLoader (gemäß erickson Antwort) und es würde so aussehen:
ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
for (Pet implClass : loader) {
System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
}
Beachten Sie, dass Sie dies Pet
als ServiceProviderInterface (SPI) definieren und dessen Implementierungen deklarieren müssen, damit dies funktioniert . Sie tun das , indem eine Datei bei der Erstellung resources/META-INF/services
mit dem Namen examples.reflections.Pet
und erklären alle Implementierungen Pet
darin
examples.reflections.Dog
examples.reflections.Cat
Annotation auf Paketebene . Hier ist ein Beispiel:
Package[] packages = Package.getPackages();
for (Package p : packages) {
MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
if (annotation != null) {
Class<?>[] implementations = annotation.implementationsOfPet();
for (Class<?> impl : implementations) {
System.out.println(impl.getSimpleName());
}
}
}
und die Annotationsdefinition:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PACKAGE)
public @interface MyPackageAnnotation {
Class<?>[] implementationsOfPet() default {};
}
und Sie müssen die Annotation auf Paketebene in einer Datei mit dem Namen package-info.java
in diesem Paket deklarieren . Hier sind Beispielinhalte:
@MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
package examples.reflections;
Beachten Sie, dass nur Pakete, die dem ClassLoader zu diesem Zeitpunkt bekannt sind, durch einen Aufruf von geladen werden Package.getPackages()
.
Darüber hinaus gibt es andere auf URLClassLoader basierende Ansätze, die immer auf bereits geladene Klassen beschränkt sind, es sei denn, Sie führen eine verzeichnisbasierte Suche durch.
Was erickson gesagt hat, aber wenn Sie es trotzdem tun möchten, schauen Sie sich Reflections an . Von ihrer Seite:
Mit Reflections können Sie Ihre Metadaten abfragen nach:
- Holen Sie sich alle Untertypen eines Typs
- Alle Typen mit Anmerkungen versehen
- Lassen Sie alle Typen mit Anmerkungen versehen, einschließlich der Übereinstimmung der Anmerkungsparameter
- Lassen Sie alle Methoden mit einigen kommentieren
new Reflections("my.package").getSubTypesOf(MyInterface.class)
Im Allgemeinen ist dies teuer. Um Reflection verwenden zu können, muss die Klasse geladen werden. Wenn Sie jede im Klassenpfad verfügbare Klasse laden möchten, nimmt dies Zeit und Speicher in Anspruch und wird nicht empfohlen.
Wenn Sie dies vermeiden möchten, müssen Sie einen eigenen Parser für Klassendateien implementieren, der effizienter arbeitet als Reflexion. Eine Bytecode-Engineering-Bibliothek kann bei diesem Ansatz hilfreich sein.
Der Service Provider-Mechanismus ist das herkömmliche Mittel zum Auflisten von Implementierungen eines steckbaren Dienstes und hat sich mit der Einführung von Project Jigsaw (Modulen) in Java 9 etabliert. Verwenden Sie den ServiceLoader
in Java 6 oder implementieren Sie Ihren eigenen in früheren Versionen. Ich habe in einer anderen Antwort ein Beispiel angegeben .
META-INF/services/java.sql.Driver
Der Frühling hat eine ziemlich einfache Möglichkeit, dies zu erreichen:
public interface ITask {
void doStuff();
}
@Component
public class MyTask implements ITask {
public void doStuff(){}
}
Anschließend können Sie eine Typliste automatisch verdrahten, ITask
und Spring füllt sie mit allen Implementierungen:
@Service
public class TaskService {
@Autowired
private List<ITask> tasks;
}
Der robusteste Mechanismus zum Auflisten aller Klassen, die eine bestimmte Schnittstelle implementieren, ist derzeit ClassGraph , da er ein möglichst breites Spektrum von Klassenpfadspezifikationsmechanismen verarbeitet , einschließlich des neuen JPMS-Modulsystems. (Ich bin der Autor.)
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
.enableClassInfo().scan()) {
for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
foundImplementingClass(ci); // Do something with the ClassInfo object
}
}
Ja, der erste Schritt besteht darin, "alle" Klassen zu identifizieren, die Sie interessiert haben. Wenn Sie diese Informationen bereits haben, können Sie sie auflisten und mit instanceof die Beziehung überprüfen. Ein verwandter Artikel ist hier: http://www.javaworld.com/javaworld/javatips/jw-javatip113.html
Was Erikson sagte, ist das Beste. Hier ist ein verwandter Frage- und Antwort-Thread - http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html
Mit der Apache BCEL-Bibliothek können Sie Klassen lesen, ohne sie zu laden. Ich glaube, es wird schneller gehen, da Sie den Überprüfungsschritt überspringen sollten. Das andere Problem beim Laden aller Klassen mit dem Klassenladeprogramm besteht darin, dass Sie eine enorme Speicherbelastung erleiden und versehentlich statische Codeblöcke ausführen, die Sie wahrscheinlich nicht möchten.
Der Link zur Apache BCEL-Bibliothek - http://jakarta.apache.org/bcel/
Mit ClassGraph ist es ziemlich einfach:
Grooviger Code zum Finden von Implementierungen von my.package.MyInterface
:
@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}
scan().withCloseable { ... }
Groovy aufrufen oder Try-with-Resources in Java verwenden: github.com/classgraph/classgraph/wiki/… Auch der letzte Teil sollte .name
nicht sein .className
, da dies .getName()
die richtige Methode ist, um den Namen einer Klasse abzurufen von einem ClassInfo
Objekt.
Eine neue Version der Antwort von @ kaybee99, die nun jedoch zurückgibt, was der Benutzer verlangt: die Implementierungen ...
Der Frühling hat eine ziemlich einfache Möglichkeit, dies zu erreichen:
public interface ITask {
void doStuff();
default ITask getImplementation() {
return this;
}
}
@Component
public class MyTask implements ITask {
public void doStuff(){}
}
Anschließend können Sie eine Typliste automatisch verdrahten, ITask
und Spring füllt sie mit allen Implementierungen:
@Service
public class TaskService {
@Autowired(required = false)
private List<ITask> tasks;
if ( tasks != null)
for (ITask<?> taskImpl: tasks) {
taskImpl.doStuff();
}
}
Ich bin auf dasselbe Problem gestoßen. Meine Lösung bestand darin, mithilfe von Reflection alle Methoden in einer ObjectFactory-Klasse zu untersuchen und diejenigen zu eliminieren, bei denen es sich nicht um createXXX () -Methoden handelte, die eine Instanz eines meiner gebundenen POJOs zurückgeben. Jede so entdeckte Klasse wird einem Class [] -Array hinzugefügt, das dann an den JAXBContext-Instanziierungsaufruf übergeben wurde. Dies funktioniert gut, da nur die ObjectFactory-Klasse geladen werden muss, die ohnehin benötigt wurde. Ich muss nur die ObjectFactory-Klasse verwalten, eine Aufgabe, die entweder von Hand ausgeführt wird (in meinem Fall, weil ich mit POJOs begonnen und Schema verwendet habe) oder von xjc nach Bedarf generiert werden kann. In jedem Fall ist es performant, einfach und effektiv.