Blog Eclipse

L’injection dans Eclipse 4

L’injection dans Eclipse 4

Dans tous les tutoriaux que l’on peut trouver sur Eclipse 4, beaucoup considèrent que le lecteur manipule depuis longtemps les annotations et l’injection, et peu d’informations sont données sur le fonctionnement interne de tous ces mécanismes.

Le but de ce premier article est de démystifier les annotations et leur fonctionnement, et notamment celle pour le mécanisme de base de l’injection.

Je détaillerai ensuite dans un autre article comment utiliser les autres annotations.

J’espère qu’il vous aidera à mieux comprendre la puissance de ce concept et l’intérêt de les utiliser dans Eclipse 4.

 

Qu’est ce qu’une annotation ?

L’annotation est définie par le langage Java dès sa version 1.5. Il s’agit simplement d’un élément du langage qui peut être associé à une classe, une méthode, un constructeur, un champ ou un paramètre.

Elle se définit dans un fichier java du nom de l’annotation, et se déclare avec la notation @interface. L’annotation doit aussi définir sa cible d’application directement dans sa définition. Par exemple l’annotation d’injection est définie de la manière suivante :

Annotation @Inject

Cette définition se fait également par des annotations réservées pour les annotations !

Il est également possible de définir des annotations avec des paramètres, en créant à l’intérieur des méthodes donnant le type de la valeur et leur valeur par défaut. Par exemple, l’annotation de préférence d’Eclipse 4 est définie de la manière suivante :

@Preference

Je ne développerai pas plus la déclaration des annotations en Java, de nombreux articles le font déjà, l’essentiel étant de retenir qu’une annotation s’applique à une cible et peut donc être utilisée par la suite, en utilisant l’introspection Java.

 

Le rôle de l’introspection java.

Les annotations n’ont aucun comportement dynamique associé dans le langage. Pour leur donner un sens, il faut qu’à un moment donné, la classe contenant les annotations soit introspectée pour analyser ses annotations et pour lui appliquer un traitement particulier.

Ce traitement peut, par exemple, intervenir lors de la compilation de la classe pour générer du code complémentaire. Dans ce cas, c’est le compilateur qui analyse le code, et effectue le comportement.

Il faut donc bien comprendre que les annotations ne sont rien sans un traitement derrière qu’il faut effectuer.

C’est donc, lors du runtime, qu’Eclipse 4 va exploiter les classes annotées qu’il va traiter au moment voulu !

Toute la difficulté est donc de connaître ces moments et de savoir à quoi vous avez accès. Eclipse 4 dispose d’un renderer qui permet d’afficher votre application décrite dans le modèle d’application.

C’est donc ce renderer qui va analyser la classe traitée à un moment donné (par exemple un Part est analysé au moment de l’affichage d’une perspective), et qui va ensuite appeler les méthodes possédant telle ou telle annotation.

Tout se passe donc par introspection et la logique séquentielle de votre programme va en être un peu bouleversée (la recherche dans la pile d’appel lors du debug aussi !).

Prenons l’exemple de l’annotation @PostConstruct, que l’on peut poser sur une méthode. Il faut savoir que cette méthode ne sera appelée qu’une fois et après que tous les comportements d’injection aient été appliqués (j’y reviendrai tout à l’heure).

Donc si on ne connait pas la logique du moteur qui exploite les annotations, il va être difficile de comprendre le séquencement d’appel et les endroits où annoter.

Détaillons donc maintenant un peu plus le fonctionnement de l’annotation d’injection, qui est la base de tout le système, et qui est également implicitement utilisée par toutes les autres annotations ensuite.

 

L’annotation @Inject

Le mécanisme d’injection consiste à déléguer l’instanciation des objets à un injecteur qui aura pour rôle ensuite d’injecter les instances créées aux endroits voulus.

Comme je l’ai dit précédemment, cette annotation ne peut fonctionner que si elle est exploitée dans un processus d’instanciation géré par l’injecteur.

Donc si vous avez écrit une classe avec des annotations @Inject, n’espérez pas que le mécanisme soit pris en charge, si vous instanciez votre classe avec un ‘new’ traditionnel en java !

Il faudra appeler l’injecteur (Cf ContextInjectionFactory) pour construire chaque objet afin que ses annotations soient traitées. Le contexte d’injection peut alors être reçu par … injection ! Le schéma suivant résume la situation :

 

Utilisation de la ContextInjectionFactory

 

Concernant l’ordre d’appel, l’annotation @Inject fonctionne de la manière suivante :

  1. appel du constructeur
  2. initialisation des champs
  3. appel des méthodes injectées.

On obtient donc la situation suivante :

Ordre d'appel de @Inject

Il faut faire attention, car la logique est encore subtile pour chaque partie :

1. Appel du constructeur

Il se fait directement par l’injecteur. Mais quel constructeur choisir si plusieurs ont été définis puisqu’on ne l’appelle pas explicitement ?

Pour le constructeur, l’injecteur va choisir celui qui possède une annotation d’injection et qui possède le maximum de paramètres injectables. Ainsi si vous avez un constructeur avec 3 paramètres, dont 2 seulement peuvent être injectés, ce constructeur ne sera pas appelé.

Autre petite caractéristique qui a son importance, le constructeur n’a jamais accès aux champs injectés de la classe ! Car ils seront initialisés après son appel. Par contre, il peut manipuler les champs qui ne possèdent pas d’annotation @Inject.

2. Injection des champs

Chaque champ de la classe, précédé d’une annotation @Inject sera initialisé, une fois le constructeur appelé et ayant marché (s’il y a eu une exception, tout s’arrête)…

Si un champ ne peut pas être injecté (valeur nulle), l’injecteur génère une exception.  Si ce cas peut se produire, il faut précéder le champ de l’annotation @Optional.

Si votre objet a des champs hérités injectés, ces derniers seront initialisés avant.

 

3. Appel des méthodes injectées.

L’instanciation de la classe possédant les annotations va se terminer par l’appel de chaque méthode possédant une annotation @Inject.

Tous les paramètres attendus par ces méthodes doivent pouvoir être injectés (à moins qu’il soient précédés d’une annotation @Optional) sous peine d’exception.

L’ordre d’appel des méthodes injectées est indéterminé. Toutefois, les méthodes injectées héritées sont appelées avant celles de la classe.

 

Et voilà votre objet a été initialisé automatiquement intégralement… mais ce n’est pas tout !

Un comportement supplémentaire  à connaître :

C’est aussi là que l’injection apporte sa magie, car chaque méthode sera réappellée automatiquement, si l’un de ses paramètres change, et ce, à tout moment. De même un attribut injecté sera remis à jour automatiquement si la dernière valeur injectée a changé. Ainsi si vous définissez une méthode @Inject dont l’un des paramètres est par exemple la sélection courante, cette méthode sera appelée à chaque modification de la sélection courante.

Ce mécanisme fonctionne avec tous les objets reçus et remplace partiellement le mécanisme des listeners, plus traditionnel en Eclipse 3.

Ainsi dans l’exemple suivant :

Eclipse 4 selection injection

la méthode sera appelée lors de la construction de l’objet qui la contient, mais également à chaque moment où l’un des deux objets passés en paramètre change de valeur dans l’injecteur. Pour ce cas, l’Adapter ne changera pas, car c’est un service global dans Eclipse 4, par contre, la sélection active (passée avec une annotation @Named), changera régulièrement, et sera automatiquement réinjectée dans cette méthode !

Remarque : On notera dans cet exemple, que le paramètre injecté étant de type ‘Object’, il doit être récupéré par un nom unique, qui, dans notre cas de sélection, est fourni par l’interface IServiceConstants d’Eclipse 4. Il serait aussi possible de remplacer le type Object attendu, par le type Rental. Dans ce cas, la méthode ne sera appelée que si la sélection courante est une instance de Rental.

Voilà donc l’une de subtilités de l’injection qui est aussi utilisée dans Eclipse 4 ! Et attention, là nous n’avons décrit que le mécanisme d’injection, nous n’avons pas encore parlé des annotations spécifiques utilisés par le renderer (objet d’un autre article).

 

Quatre annotations complémentaires pour finir.

Mais l’annotation @Inject seule ne suffit pas à couvrir tous les cas. En effet, l’ordre d’appel des méthodes annotées avec @Inject n’étant pas déterminé, il peut être difficile de s’y retrouver. Certains paramètres également peuvent correspondre à plusieurs instances possibles dans l’injecteur, ou peuvent être optionnels. Le développeur dispose donc d’annotations complémentaires permettant de traiter ces différents cas.

@PostConstruct

Cette annotation ne s’applique que sur une méthode qui sera appelée une fois toutes les injections terminées.  Cette annotation permet ainsi de finaliser l’initialisation d’un objet. Elle est utilisée par exemple pour créer le contenu d’une vue dans Eclipse 4, pour initialiser un Addon, pour brancher des listeners, …

@PreDestroy

Comme @PostConstruct, elle ne s’applique que sur une méthode qui sera appelée juste avant la destruction d’un objet. On peut se servir de cette annotation pour indiquer une méthode qui enlevera les éventuels listeners, ou qui gérera les subtilités de désallocation.

@Optional

A mettre devant un paramètre de méthode injecté ou un champ de classe injecté, pour indiquer que l’on gère le fait qu’il n’y ait pas de valeur.  N’oubliez pas que si aucune valeur ne peut être injectée, une exception sera levée.

@Named

Permet, associé à un nom passé en paramètre de l’annotation, de retrouver un objet nommé stocké dans l’injecteur. On utilise cette annotation quand le type recherché peut avoir plusieurs instances possibles.

 

Que peut on finalement injecter ?

Là il faut aussi être intuitif, et on peut arriver à s’en sortir presque à chaque fois avec un peu de réflexion (c’est le cas de le dire ! )…

Déjà il faut bien comprendre qu’Eclipse va gérer pour vous des contextes d’injection (IEclipseContext), hiérarchiques, initialisés au fur et à mesure des traitements.

Par exemple, quand l’application démarre, un contexte global est créé. Ce contexte va contenir tous les services OSGi utilisés par Eclipse 4. Puis quand le renderer va s’exécuter, il va créer un contexte pour l’application model (fils du contexte principal). Ensuite, une perspective se créé, elle aura aussi son propre contexte, relié au contexte parent. Idem pour le Part.

Donc, logiquement, le renderer va vous fournir les instances nécessaire pour que votre méthode puisse fonctionner.

C’est pour cela que si on injecte une instance de Component dans la méthode de création du contenu d’une vue, ce Component est forcément le parent dans lequel la vue doit se créér ! Ce n’est pas magique . C’est le comportement dynamique du renderer qui à un moment donné, l’initialise pour vous.

Si vous arrivez ensuite dans un autre part, le Component injecté sera bien sur différent !

On peut donc se dire, que si on a besoin de quelque chose à un endroit, normalement il est disponible !

Plus précisément, dans Eclipse 4, les objets fournissant un nouveau contexte dans le cadre de l’application model, héritent de l’interface MContext. On trouve par exemple, MApplication, MWindow, MPerspective, MPart, MPopupMenu. Sur chacun de ces objets, on pourra récupérer le contexte en appelant la méthode getContext().

Dans un prochain article je vous détaillerai les autres annotations d’Eclipse 4 (et le moment où celles ci sont utilisées !), ainsi que la manière de gérer vos contextes.

Laisser un commentaire