Nutzt ihr aktiv Annotations und wie sind eure Erfahrungen damit?
Mit aktiv meine ich nicht JPA und dann @Entity, @ID etc. und auch nicht @Override.
Sondern selbsterstellte AnnotationTypes und deren Auswertung zur Laufzeit.
Ich habe z.B. so einen Fall, bei dem ich dies schön mit Annotations lösen könnte, bin mir aber noch nicht ganz schlüssig ob dies wirklich so eine Gute Idee ist.
Ursprünglich hatte ich eine Interface mit vielen Methoden.
Dazu Klassen die dieses Interface implementieren, allerdings bleiben viele (die meisten) der Methodenrümpfe leer.
Jede Instanz wird an einer Zentralen Stelle registriert, und ein Aufruf an der zentralen Stelle ruft dann die Methoden der jeweiligen Klassen auf.
interface I {
void a(); void b(); void(c);...
}
class Reg {
List<I> list = new ArrayList();
void doA() {
for(I i: list) {
i.a();
}
}
void doB() {
for(I i: list) {
i.b();
}
}
void doC() {
for(I i: list) {
i.c();
}
}
}
class ImplI implements I {
void a() {
System.out.println("ImplI::a()");
}
void b() {/* Not used */}
void c() {/* Not used */}
}
Idee ist nun eine Annotation zu erstellen
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface A {}
class Reg {
List<Object> list = new ArrayList();
void doA() {
do(A.class);
}
void doB() {
do(B.class);
}
void doC() {
do(C.class);
}
private do(Class anno) {
for(Object i : list) {
Class c = i.getClass();
Method[] methods = c.getMethods();
for(Method m : methods) {
if(m.getAnnotation(anno) != null) {
try {
m.invoke(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
class ImplI {
@A
void a() {
System.out.println("ImplI::a()");
}
}
Rückgabewerte sind kein Problem, Parameterlisten können auch gehandelt werden, obwohl hier etwas Typsicherheit verloren geht.
Abgesehen davon nutze ich für meine Projekte gerne Sachen wie z.B. google-guice. Hierfür implementiere ich oft noch eigene TypeListener, welche mit eigenen Annotationen arbeiten ,z.B. wenn ich eigene Logger injecten möchte oder passend zum Blogeintrag eigene Event<>-Objekte:
So wie es Tomate_Salat schon schrieb, mache ich es auch (nur nicht gerne ). Zur Ansteuerung von Frameworks, die auf Annotationen setzen (DI-Frameworks, JPA, Constraing Validation API), habe ich mir auch schon Annotationen selbst implementiert. Einen eigenen Annotation-Processor habe ich noch nie geschrieben. Glaube auch nicht, dass man sowas wirklich braucht. I.d.R. gibt es die -je nach Einsatzzweck- bereits innerhalb von Frameworks/Libraries.
Der von Dir hier skizzierte Anwendungsfall scheint mir z.B. ein Fall für DI-Frameworks zu sein.
mit OO, Klassen, Interface, Enums und nur als halbe Compiler-Spielerei Generics, deren Typen zur Laufzeit auch nicht abfragbar sind,
ist nun wirklich ein zusammenhängendes harmonische Systen für alle Aufgaben vorhanden
für das Reg-Beispiel ginge eine Enum mit Werten A, B, C,
in der Impl-Klasse
switch(e) {
case A:
a();
break;
default:
break;
}
}```
bisschen länglich und für das switch bekomme ich bestimmt Haue ;)
aber dafür noch manche OO-Möglichkeiten:
- Verlegung in Basisklasse/ Hilfsklasse für einige Klassen mit gleichen Interface,
- Logging der Aufrufe, Exception bei falschen ExecuteType,
- weitere Parameter, aktuelle Zustands-/ Berechtigungsprüfungen, Rückgabewert
einfach das ganze OO-Programm statt nur mit Annotation irgendwas starres, für die IDE/ für jeden Leser unsichtbares Aufrufsysem eingebaut zu haben
[quote=SlaterB]Reflection ist für Normalfall kein legitimes Mittel,
außer mal im halben Notfall instanceof-Test[/quote]
Sehe ich nicht so. Wenn man sich so Sachen wie DI, Eventbus-Systeme, JPA anschaut - dann hat das durchaus seine Berechtigung.
Stimmt so nicht. Ausm Stehgreif weiß ich jetzt nicht direkt welche und welche nicht. Aber ich mein: Generics in Vererbungen, Anonymen Klassen und Felddeklarationen sind abrufbar. Letzteres gut zu sehen an dem Quellcode, welchen ich zuletzt in diesem Thema gespostet habe, denn dieser Injected mir Felder welche z.B. so aussehen:
@InjectEvent
private Event<Greeting> greetingEvent;
public Event<Greeting> getGreetingEvent() {
return greetingEvent;
}
public void fire(String name) {
((Greeting) greetingEvent).onGreet(name);
}
}```
Ah, gut dass das nicht ganz so “Alien-Technologien” sind.
DI ist schön, keine Frage geht aber nicht wirklich, da das Lifecycle-Management bereits vom Verwendeten Framework übernommen wird und dies altersbedingt stark auf Vererbung und abstrakte Klassen baut.
Erweiterbarkeit leidet dadurch extem. Jetzt noch GUICE draufpacken ist keine Option. Zumindest vorerst.
Da werde ich noch ein wenig überlegen, was ich nun genau anstellen werde.
Der Charme dabei ist, dass ich lediglich an ein paar Strategischen Punkten Annotationen bräuchte und die Domäne so bleiben könnte wie sie ist. Keine weiteren Vererbungen, keine unnötigen Methoden. Einige dieser Interface-Methoden sorgen letztlich nur dafür das sie an eine Domänen-Methode delegieren.
In normalem Anwendungscode sollte Reflection mMn nicht auftauchen. Sowas gehört in ein Framework und sollte daher nicht direkt genutzt werden, außer in dem vom @nillehammer beschriebenen Umfang. Die zitierten Anwendungsfälle sind alles Frameworks, die im Hintergrund arbeiten und damit den Anwendungscode verbessern.
mit einem Framework Textbeschreibung von Daten auf unbekannte Klassen abzubilden ist eben auch kein genannter Normalfall, da geht es nunmal nicht anders,
wobei, selbst wenn die Klassen bekannt wären will natürlich niemand Massencode a la
if (classText.equals("Person")) {
Person p = new Person();
for (SubData .. {
if (fieldText.equals("Name")) {
p.setName(..value);
}
}
das ist ja keine Programmierung, sondern Arbeit
das kann ruhig ganz allgemein abgearbeitet werden
übrigens bin ich da auch strenger Verfechter, dass Datenklassen wie Person frei von Annotations zu halten sind,
die Persistierung muss alleine klar kommen, evtl. mit zusätzlichen XML-Beschreibungsklassen, die kann man ja beliebig ablegen
immer der Testgedanke: was ist wenn 5 verschiedene Persistierungsframeworks gleichzeitig damit arbeiten sollen? nichts davon darf in die Pojo-Klassen
wenn Anwendungslogik in 100 Klassen auf einmal per RMI übertragen werden muss, dann habe ich dafür auch eine vom restlichen Code unabhängige Reflection-Lösung,
aber da wo man direkt im Code arbeitet, Typen A, B, C direkt vergibt, da geht es auf humanen Wegen
Wenn du schon bei Java 8 bist (weiß ja nicht, wo das Projekt steht), dann könntest du einfach leere Implementierungen direkt im Interface per default anbieten. Und ich finde die erste Lösung auch übersichtlicher, zumal bei der Annotation/Reflection-Lösung die Performance leidet (falls wichtig) und du in deiner do-Methode u.U. Sonderfälle beachten musst (überschriebene Methoden, andere Parameter (u.U. mit Vererbung)).
Ohne Java 8 und Default-Methoden würde ich aber wahrscheinlich auch die Annotations-Lösung nehmen.
Eine Andere Möglichkeit wäre noch für jede Methode ein Interface anzubieten und statt Annotations einfach mit instanceof zu prüfen, ob die Klasse das Interface implementiert (und damit die Methode hat).