Appuyez sur Entrée pour voir vos résultats ou Echap pour annuler.

DIY 3 – Un conteneur d’injection de dépendances

L’injection de dépendances est un patron de conception assez facile à maîtriser qu’on retrouve dans pratiquement toutes les applications, partout dans le code. Toujours dans le cadre de la série DIY (Do It Yourself), je vous présente le prochain article qui traite d’un conteneur d’injection de dépendances.

Si vous avez manqué les deux premiers articles de la série, vous pouvez la suivre en utilisant les catégories du blogue.

Les sources de cet article sont sur GitHub.

Objectif

Démontrer comment fonctionne un conteneur d’injection de dépendances. Java sera utilisé pour démontrer le concept, mais tout langage orienté-objet supportant la réflexion pourrait faire.

Plan

  1. Créer les annotations
  2. Coder l’application de test
  3. Édifier un conteneur DI
  4. Élaborer un scanner de classe
  5. Accoucher d’un scanner d’attribut

Prérequis

  • Une bonne connaissance de l’orienté-objet
  • Un bonne compréhension du concept de réflexion

Étape 1 – Créer les annotations

L’injection de dépendances (DI) permet de respecter le principe d’inversion de dépendances ou DIP (voir « D » de SOLID). Le rôle du conteneur DI est d’instancier et d’injecter les objets directement dans ses attributs, soit via le constructeur, via un mutateur (setter) ou par réflexion, comme aujourd’hui.

À titre d’exemple, j’utiliserai les annotations de Java pour marquer les classes comme étant « injectable » et les propriétés comme étant « injectées ». Créons la première indiquant au conteneur DI que la classe est « injectable » :

La rétention doit être définie pour que l’annotation persiste à l’exécution, sinon elle ne sera disponible qu’à la compilation. Nos classes dont les instances seront injectées devront être annotées de cette dernière. Pour indiquer qu’un attribut de classe soit « injecté » par le conteneur, nous aurons besoin d’une deuxième annotation :

Étape 2 – Coder l’application de test

Supposons un cas très simple, soit une application « console » qui affiche une liste d’utilisateurs, mais dans une architecture traditionnelle. Notre application utilisera un service d’utilisateur responsable de la logique d’affaires, qui lui-même utilisera un dépôt (repository) pour simuler un accès à la base de données.

Dans cet exemple, je propose d’ajouter l’annotation directement sur l’interface. Le conteneur sera responsable de trouver l’implémentation à injecter, supposant qu’il n’y en ait qu’une. Pour notre service, ce sera tout aussi simple :

Pour l’implémentation, le dépôt (repository) retournera une liste d’utilisateurs codée-dure :

Le service, quant à lui, ne fera que déléguer l’appel au dépôt dont l’instance lui sera injectée par le conteneur :

Pour finir cette application, il nous faut une classe qui appelle :

Étape 3 – Édifier un conteneur DI

La responsabilité du conteneur est d’indexer les classes « injectables » dans un registre avec leur implémentation. L’instance du conteneur sera construite par le patron de la fabrique (factory).

Lorsqu’un appelant voudra une instance, cette dernière lui sera retournée depuis une cache, implémentant les patrons Lazy Loading et Singleton pour nos instances.

À la création, le conteneur va lister les classes récursivement depuis un package de base. Toutes les classes annotées de @Injectable  seront conservées dans une Map avec le type de leur implémentation. Lorsqu’on demandera une instance au conteneur, si elle n’est pas dans la cache, nous en créerons une nouvelle et la mettrons en cache. Le constructeur ressemble à :

Pour l’implémentation de la méthode abstraite, nous appelerons une méthode pour instancier la classe en paramètre, puis nous injecterons les dépendances « injectées » de cette dernière :

La méthode getInstanceOf a la responsabilité de créer une instance de la classe en paramètre si elle n’existe pas en cache, sinon l’instance en cache est retournée. C’est ici que le singleton prend forme.

Avec la classe instanciée, nous utiliserons la réflexion pour lister les attributs annotés de @Injected, puis nous allons vérifier dans le registre pour trouver l’implémentation de l’interface et allons finalement appeler  getClassInstance de façon récursive pour injecter les dépendances dans cette dépendance 🤯.

Étape 4 – Élaborer un scanner de classe

Comme vous avez pu constater ci-dessus, le conteneur DI appel un scanner de classe dans son constructeur pour lister les classes et interfaces annotées de @Injectable. Ce scanner doit lister récursivement les classes d’un package de base. Ici aussi, la fabrique crée l’instance :

Pour lister les classes d’un package, il faut utiliser le ClassLoader (réflexion) :

Le retour de cette méthode contiendra la liste des classes qui sont présentement chargées par le ClassLoader. Donc pour trouver les classes annotées, nous utiliserons (encore) la réflexion afin d’obtenir les annotations :

Pour obtenir l’instance de Class qui correspond à un fichier du ClassLoader, nous utiliserons le chemin d’accès, remplacerons les « \ » en « . » (notation package), retirerons tout ce qui vient avant le package de base (répertoire source) et supprimerons l’extension du fichier (.class). Ceci nous donnera un nom de classe complet avec son package, comme com.ezoqc.blog.di.bootstrap.demo.UserService :

Étape 5 – Accoucher d’un scanner d’attribut

Comme vous avez pu voir dans la méthode injectFields du conteneur DI, un scanner d’attribut est utilisé pour obtenir les attributs de l’objet instancié qui sont annotés de @Injected. Une fabrique instanciera ce dernier, tout comme le scanner de classe :

Toujours par réflexion, nous listons les attributs et validons la présence de l’annotation :

Finalement, on attache le tout dans un main dans TestApp :

Le résultat :

Conclusion

Aujourd’hui, vous avez analysé comment fonctionne un conteneur d’injection de dépendances. Je vous rappelle que vous pouvez suivre la série via les catégories du blogue pour ne rien manquer. Je vous invite aussi à partager cet article avec vos collègues de travail et à vous inscrire au blogue. Nous sommes fiers de vous offrir du contenu gratuit et sans pub, donc vous pouvez être certains que nous ne vous spamerons pas par courriel 😉

Merci de nous lire! À la semaine prochaine.

 

Suivez-nous par courriel!

Saisissez votre adresse courriel pour vous abonner au blog d'Ezo et recevoir une notification de chaque nouvel article par email.