Nous utilisons des liens statiques pour les propriétés d'objet à l'aide de lambdas

Il est arrivé historiquement qu'en Java pour les propriétés des objets (propriétés), aucune entité physique ne soit fournie. Les propriétés en Java sont quelques conventions pour nommer les champs et les méthodes d'accès (accesseurs) pour eux. Et, bien que la présence de propriétés physiques dans le langage simplifierait beaucoup de cas (à partir de la génération idiote de getter-setters), il semble que dans un proche avenir, la situation à Java ne changera pas.


Néanmoins, lors du développement d'applications commerciales multicouches et de l'utilisation de divers cadres pour le mappage et la liaison de données, il est souvent nécessaire de transmettre une référence à une propriété d'objet. Voyons quelles sont les options pour cela.


Utiliser le nom de la propriété


Jusqu'à présent, la seule façon généralement acceptée de faire référence à une propriété d'objet est une chaîne avec son nom. La bibliothèque sous-jacente utilise la réflexion ou l'introspection pour rechercher des méthodes d'accesseur et des champs d'accès. Pour référencer des objets imbriqués, la notation suivante est généralement utilisée:


person.contact.address.city 

Le problème avec cette méthode est le manque de contrôle sur l'orthographe du nom et du type de propriété avec tout ce qu'elle implique:


  • Il n'y a pas de contrôle d'erreur au stade de la compilation. Vous pouvez faire une erreur dans le nom, vous pouvez l'appliquer à la mauvaise classe, le type de la propriété n'est pas contrôlé. Nous devons également écrire des tests assez stupides.
  • Aucun support de l'IDE. Très fatigué lorsque vous mepe plus de 200 champs. C'est bien s'il y a un mois de juin pour cela, qui peut être désactivé.
  • Refactorisation de code sophistiquée. Changez le nom du champ, et tout de suite beaucoup de choses tomberont. Les bons IDE feront également ressortir des centaines d'endroits où un mot similaire se produit.
  • Support et analyse de code. Nous voulons voir où la propriété est utilisée, mais «Find Usages» n'affichera pas la chaîne.

Par conséquent, nous souhaitons toujours disposer d'une référence de propriété statique de type sécurisé. Un getter est le meilleur candidat pour ce rôle, car:


  • Lié à une classe spécifique
  • Contient le nom de la propriété.
  • A un type

Comment puis-je me référer à un getter?


Procuration


L'une des façons intéressantes consiste à proxy (ou à mouiller) des objets pour intercepter la chaîne d'appel getter, qui est utilisée dans certaines bibliothèques: Mockito , QueryDSL , BeanPath . À propos du dernier sur Habré, il y avait un article de l'auteur.
L'idée est assez simple, mais pas banale à mettre en œuvre (un exemple de l'article mentionné).


 Account account = root(Account.class); tableBuilder.addColumn( $( account.getCustomer().getName() ) ); 

En utilisant la génération de code dynamique, une classe proxy spéciale est créée qui hérite de la classe du bean et intercepte tous les appels getter de la chaîne, construisant un chemin dans la variable ThreadLocal. Dans ce cas, l'appel de ces getters de l'objet ne se produit pas.


Dans cet article, nous considérerons une méthode alternative.


Liens de méthode


Avec l'avènement de Java 8, lambdas et la possibilité d'utiliser des références de méthode sont apparus. Par conséquent, il serait naturel d'avoir quelque chose comme:


 Person person = … assertEquals("name", $(Person::getName).getPath()); 

La méthode $ accepte le lambda suivant dans lequel la référence getter est passée:


 public interface MethodReferenceLambda<BEAN, TYPE> extends Function<BEAN, TYPE>, Serializable {} ... public static <BEAN, TYPE> BeanProperty<BEAN, TYPE> $(MethodReferenceLambda<BEAN, TYPE> methodReferenceLambda) 

Le problème est qu'en raison de l'effacement des types, il n'y a aucun moyen d'obtenir les types BEAN et TYPE lors de l'exécution, et il n'y a pas non plus d'informations sur le nom du getter: la méthode qui est appelée «en dehors» est Function.apply ().


Néanmoins, il existe une certaine astuce - c'est l'utilisation de lambda sérialisé.


 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(); 

La classe SerializedLambda contient toutes les informations nécessaires sur la classe et la méthode appelées. Vient ensuite une question de technologie.
Comme je travaille beaucoup avec des structures de données, cette méthode m'a encouragé à écrire une petite bibliothèque pour l'accès statique aux propriétés.


Bibliothèque BeanRef


L'utilisation de la bibliothèque ressemble à ceci:


 Person person = ... //     final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); assertEquals("contact.address.city", personCityProperty.getPath()); 

et ne nécessite pas la magie de la génération de code et des dépendances tierces. Au lieu d'une chaîne getter, une chaîne lambda est utilisée en référence aux getters. Dans le même temps, la sécurité des types est respectée et la complétion automatique basée sur IDE fonctionne plutôt bien:


Vous pouvez utiliser le nom du getter en notation standard (getXXX () / isXXX ()) et non standard (xxx ()). La bibliothèque essaiera de trouver le setter correspondant, et s'il est absent, la propriété est déclarée en lecture seule.


Pour accélérer les performances, les propriétés résolues sont mises en cache et lorsque vous l'appelez à nouveau avec le même lambda, le résultat est déjà enregistré.


En plus du nom de la propriété / du chemin, à l'aide de l'objet BeanPath, vous pouvez accéder à la valeur de propriété de l'objet:


 Person person = ... final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); String personCity = personCityProperty.get(person); 

De plus, si l'objet intermédiaire de la chaîne est nul, l'appel correspondant renverra également null au lieu de NPE. Cela simplifiera considérablement le code sans nécessiter de vérification.


Grâce à BeanPath, vous pouvez également modifier la valeur d'une propriété d'objet si elle n'est pas en lecture seule:


 personCityProperty.set(person, “Madrid”); 

En suivant la même idée - aussi peu de NPE que possible - dans ce cas, si l'un des objets intermédiaires de la chaîne est nul, la bibliothèque essaiera de le créer automatiquement et de l'enregistrer dans le champ. Pour ce faire, la propriété correspondante doit être accessible en écriture et la classe d'objet doit avoir un constructeur public sans paramètres.


À titre expérimental, la possibilité de travailler avec des collections est offerte. Pour certains cas particuliers, il est parfois nécessaire de construire des chemins en se référant aux objets de la collection. Pour ce faire, la méthode $$ est fournie, qui construit un lien vers le dernier élément de la collection (le considérant comme le seul).


 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()); 

Le projet est hébergé ici: https://github.com/throwable/beanref , les binaires sont disponibles à partir du référentiel jcenter maven.


Utile


java.beans.Introspector
La classe Introspector de Java Java standard vous permet de résoudre les propriétés du bac.


Apache Commons BeanUtils
La bibliothèque la plus complète pour travailler avec Java Beans.


Beanpath
Bibliothèque mentionnée qui fait la même chose par procuration.


Objénèse
Nous instancions un objet de n'importe quelle classe avec n'importe quel ensemble de constructeurs.


Alias ​​QueryDSL
Utilisation de classes proxy pour définir des critères dans QueryDSL


Jinq
Une bibliothèque intéressante qui utilise des lambdas pour définir des critères dans JPA. Beaucoup de magie: procuration, sérialisation de lambdas, interprétation de bytecode.

Source: https://habr.com/ru/post/fr469181/


All Articles