Historisch gesehen ist es so, dass in Java für die Eigenschaften von Objekten (Eigenschaften) keine physische Entität bereitgestellt wird. Eigenschaften in Java sind einige Konventionen bei der Benennung von Feldern und Zugriffsmethoden (Accessoren) für diese. Und obwohl das Vorhandensein physikalischer Eigenschaften in der Sprache viele Fälle vereinfachen würde (ausgehend von der albernen Generation von Getter-Setzern), scheint sich die Situation in Java in naher Zukunft nicht zu ändern.
Bei der Entwicklung mehrschichtiger Geschäftsanwendungen und der Verwendung verschiedener Frameworks zum Zuordnen und Binden von Daten ist es jedoch häufig erforderlich, einen Verweis auf eine Objekteigenschaft zu übergeben. Lassen Sie uns überlegen, welche Optionen dafür zur Verfügung stehen.
Verwenden Sie den Eigenschaftsnamen
Bisher ist die einzige allgemein akzeptierte Möglichkeit, auf eine Objekteigenschaft zu verweisen, eine Zeichenfolge mit ihrem Namen. Die zugrunde liegende Bibliothek verwendet Reflection oder Introspection, um nach Zugriffsmethoden und Zugriffsfeldern zu suchen. Um auf verschachtelte Objekte zu verweisen, wird normalerweise die folgende Notation verwendet:
person.contact.address.city
Das Problem bei dieser Methode ist das Fehlen jeglicher Kontrolle über die Schreibweise des Namens und der Art der Eigenschaft mit allem, was dies impliziert:
- In der Kompilierungsphase gibt es keine Fehlerkontrolle. Sie können einen Fehler im Namen machen, Sie können ihn auf die falsche Klasse anwenden, der Typ der Eigenschaft wird nicht kontrolliert. Wir müssen zusätzlich ziemlich dumme Tests schreiben.
- Keine Unterstützung von der IDE. Sehr müde, wenn Sie mehr als 200 Felder mepe. Es ist gut, wenn es dafür einen Juni gibt, der ausgeschaltet werden kann.
- Anspruchsvolles Code-Refactoring. Ändern Sie den Namen des Feldes, und sofort fallen viele Dinge ab. Gute IDEs bringen auch Hunderte von Stellen hervor, an denen ein ähnliches Wort vorkommt.
- Support und Code-Analyse. Wir möchten sehen, wo die Eigenschaft verwendet wird, aber "Verwendungen suchen" zeigt die Zeichenfolge nicht an.
Aus diesem Grund möchten wir weiterhin eine statische typsichere Eigenschaftsreferenz haben. Ein Getter ist der beste Kandidat für diese Rolle, weil:
- An eine bestimmte Klasse gebunden
- Enthält den Namen der Eigenschaft.
- Hat Typ
Wie kann ich mich auf einen Getter beziehen?
Proxying
Eine der interessanten Möglichkeiten ist das Proxying (oder Moking) von Objekten, um die Getter-Aufrufkette abzufangen, die in einigen Bibliotheken verwendet wird: Mockito , QueryDSL , BeanPath . Über den letzten auf Habré gab es einen Artikel des Autors.
Die Idee ist recht einfach, aber nicht trivial zu implementieren (ein Beispiel aus dem genannten Artikel).
Account account = root(Account.class); tableBuilder.addColumn( $( account.getCustomer().getName() ) );
Mithilfe der dynamischen Codegenerierung wird eine spezielle Proxy-Klasse erstellt, die von der Bean-Klasse erbt und alle Getter-Aufrufe in der Kette abfängt und einen Pfad in der ThreadLocal-Variablen erstellt. In diesem Fall erfolgt der Aufruf dieser Getter des Objekts nicht.
In diesem Artikel werden wir eine alternative Methode betrachten.
Methodenlinks
Mit dem Aufkommen von Java 8 kamen Lambdas und die Möglichkeit zur Verwendung von Methodenreferenzen hinzu. Daher wäre es natürlich, etwas zu haben wie:
Person person = … assertEquals("name", $(Person::getName).getPath());
Die $ -Methode akzeptiert das folgende Lambda, in dem die Getter-Referenz übergeben wird:
public interface MethodReferenceLambda<BEAN, TYPE> extends Function<BEAN, TYPE>, Serializable {} ... public static <BEAN, TYPE> BeanProperty<BEAN, TYPE> $(MethodReferenceLambda<BEAN, TYPE> methodReferenceLambda)
Das Problem ist, dass es aufgrund des Löschens von Typen keine Möglichkeit gibt, BEAN- und TYPE-Typen zur Laufzeit abzurufen, und es gibt auch keine Informationen zum Getter-Namen: Die Methode, die als "außerhalb" bezeichnet wird, ist Function.apply ().
Trotzdem gibt es einen bestimmten Trick - dies ist die Verwendung von serialisiertem Lambda.
MethodReferenceLambda<Person,String> lambda = Person::getName(); Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace"); writeMethod.setAccessible(true); SerializedLambda serLambda = (SerializedLambda) writeMethod.invoke(lambda); String className = serLambda.getImplClass().replaceAll("/", "."); String methodName = serLambda.getImplMethodName();
Die SerializedLambda-Klasse enthält alle erforderlichen Informationen zur aufgerufenen Klasse und Methode. Weiter ist eine Frage der Technologie.
Da ich viel mit Datenstrukturen arbeite, ermutigte mich diese Methode, eine kleine Bibliothek für den statischen Zugriff auf Eigenschaften zu schreiben.
BeanRef-Bibliothek
Die Verwendung der Bibliothek sieht ungefähr so aus:
Person person = ...
und erfordert nicht die Magie der Codegenerierung und Abhängigkeiten von Drittanbietern. Anstelle einer Getterkette wird eine Lambda-Kette in Bezug auf Getter verwendet. Gleichzeitig wird die Typensicherheit respektiert und die IDE-basierte automatische Vervollständigung funktioniert recht gut:

Sie können den Getter-Namen sowohl in Standardnotation (getXXX () / isXXX ()) als auch in Nicht-Standardnotation (xxx ()) verwenden. Die Bibliothek versucht, den entsprechenden Setter zu finden. Wenn dieser nicht vorhanden ist, wird die Eigenschaft als schreibgeschützt deklariert.
Um die Leistung zu beschleunigen, werden aufgelöste Eigenschaften zwischengespeichert. Wenn Sie sie erneut mit demselben Lambda aufrufen, wird das Ergebnis bereits gespeichert.
Zusätzlich zum Eigenschafts- / Pfadnamen können Sie mit dem BeanPath-Objekt auf den Eigenschaftswert des Objekts zugreifen:
Person person = ... final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); String personCity = personCityProperty.get(person);
Wenn das Zwischenobjekt in der Kette null ist, gibt der entsprechende Aufruf außerdem null anstelle von NPE zurück. Dies vereinfacht den Code erheblich, ohne dass eine Überprüfung erforderlich ist.
Über BeanPath können Sie den Wert einer Objekteigenschaft auch ändern, wenn sie nicht schreibgeschützt ist:
personCityProperty.set(person, “Madrid”);
Nach der gleichen Idee - so wenig NPE wie möglich - versucht die Bibliothek in diesem Fall, wenn eines der Zwischenobjekte in der Kette null ist, es automatisch zu erstellen und im Feld zu speichern. Dazu muss die entsprechende Eigenschaft beschreibbar sein und die Objektklasse muss einen öffentlichen Konstruktor ohne Parameter haben.
Als experimentelles Feature wird die Möglichkeit geboten, mit Sammlungen zu arbeiten. In einigen speziellen Fällen ist es manchmal erforderlich, Pfade zu erstellen, die auf Objekte in der Sammlung verweisen. Zu diesem Zweck wird die Methode $$ bereitgestellt, die einen Link zum letzten Element der Sammlung erstellt (wobei davon ausgegangen wird, dass es das einzige ist).
final BeanPath<Person, String> personPhonePath = $(Person::getContact).$$(Contact::getPhoneList).$(Phone::getPhone); assertEquals("contact.phoneList.phone", personPhonePath.getPath()); assertEquals(personPhonePath.get(person), person.getContact().getPhoneList() .get(person.getContact().getPhoneList().size()-1).getPhone());
Das Projekt wird hier gehostet: https://github.com/throwable/beanref . Binärdateien sind im jcenter maven-Repository verfügbar.
Nützlich
java.beans.Introspector
Mit der Introspector-Klasse aus dem Standard-Java Java können Sie bin-Eigenschaften auflösen.
Apache Commons BeanUtils
Die umfassendste Bibliothek für die Arbeit mit Java Beans.
Bohnenpfad
Erwähnte Bibliothek, die dasselbe durch Proxy tut.
Objenesis
Wir instanziieren ein Objekt einer beliebigen Klasse mit einer beliebigen Menge von Konstruktoren.
QueryDSL-Aliase
Verwenden von Proxy-Klassen zum Festlegen von Kriterien in QueryDSL
Jinq
Eine interessante Bibliothek, die Lambdas verwendet, um Kriterien in JPA festzulegen. Viel Magie: Proxying, Serialisierung von Lambdas, Interpretation von Bytecode.