Introduction à Dagger 2

1. Introduction

Dans ce tutoriel, nous examinerons Dagger 2 - un framework d'injection de dépendances rapide et léger.

Le framework est disponible pour Java et Android, mais la haute performance dérivée de l'injection à la compilation en fait une solution de premier plan pour ce dernier.

2. Injection de dépendance

Pour rappel, l'injection de dépendances est une application concrète du principe d'inversion de contrôle plus générique dans lequel le flux du programme est contrôlé par le programme lui-même.

Il est implémenté via un composant externe qui fournit des instances d'objets (ou de dépendances) nécessaires à d'autres objets.

Et différents frameworks implémentent l'injection de dépendances de différentes manières. En particulier, l'une des différences les plus notables est de savoir si l'injection se produit au moment de l'exécution ou au moment de la compilation.

La DI d'exécution est généralement basée sur une réflexion qui est plus simple à utiliser mais plus lente à l'exécution. Spring est un exemple de framework DI d'exécution.

La DI au moment de la compilation, par contre, est basée sur la génération de code. Cela signifie que toutes les opérations lourdes sont effectuées lors de la compilation. La DI à la compilation ajoute de la complexité mais fonctionne généralement plus rapidement.

Dagger 2 entre dans cette catégorie.

3. Configuration Maven / Gradle

Pour utiliser Dagger dans un projet, nous devons ajouter la dépendance dagger à notre pom.xml :

 com.google.dagger dagger 2.16 

De plus, nous devrons également inclure le compilateur Dagger utilisé pour convertir nos classes annotées dans le code utilisé pour les injections:

 org.apache.maven.plugins maven-compiler-plugin 3.6.1    com.google.dagger dagger-compiler 2.16    

Avec cette configuration, Maven affichera le code généré dans cible / sources-générées / annotations .

Pour cette raison, nous devons probablement configurer davantage notre IDE si nous voulons utiliser l'une de ses fonctionnalités de complétion de code. Certains IDE prennent directement en charge les processeurs d'annotation tandis que d'autres peuvent avoir besoin de nous pour ajouter ce répertoire au chemin de construction.

Alternativement, si nous utilisons Android avec Gradle, nous pouvons inclure les deux dépendances:

compile 'com.google.dagger:dagger:2.16' annotationProcessor 'com.google.dagger:dagger-compiler:2.16'

Maintenant que nous avons Dagger dans notre projet, créons un exemple d'application pour voir comment cela fonctionne.

4. Mise en œuvre

Pour notre exemple, nous allons essayer de construire une voiture en injectant ses composants.

Désormais, Dagger utilise les annotations JSR-330 standard à de nombreux endroits, l'un étant @Inject.

Nous pouvons ajouter les annotations aux champs ou au constructeur. Mais, puisque Dagger ne prend pas en charge l'injection sur les champs privés , nous allons opter pour l'injection de constructeur pour préserver l'encapsulation:

public class Car { private Engine engine; private Brand brand; @Inject public Car(Engine engine, Brand brand) { this.engine = engine; this.brand = brand; } // getters and setters }

Ensuite, nous implémenterons le code pour effectuer l'injection. Plus précisément, nous allons créer:

  • un module , qui est une classe qui fournit ou construit les dépendances des objets, et
  • un composant , qui est une interface utilisée pour générer l'injecteur

Les projets complexes peuvent contenir plusieurs modules et composants, mais comme nous avons affaire à un programme très basique, un de chacun suffit.

Voyons comment les mettre en œuvre.

4.1. Module

Pour créer un module, nous devons annoter la classe avec l' annotation @Module . Cette annotation indique que la classe peut rendre les dépendances disponibles pour le conteneur:

@Module public class VehiclesModule { }

Ensuite, nous devons ajouter l' annotation @Provides sur les méthodes qui construisent nos dépendances :

@Module public class VehiclesModule { @Provides public Engine provideEngine() { return new Engine(); } @Provides @Singleton public Brand provideBrand() { return new Brand("Baeldung"); } }

Notez également que nous pouvons configurer la portée d'une dépendance donnée. Dans ce cas, nous donnons la portée singleton à notre instance de marque afin que toutes les instances de voiture partagent le même objet de marque.

4.2. Composant

Passant, nous allons créer notre interface de composant . C'est la classe qui générera des instances Car, en injectant les dépendances fournies par VehiclesModule .

En termes simples, nous avons besoin d'une signature de méthode qui renvoie une voiture et nous devons marquer la classe avec l' annotation @Component :

@Singleton @Component(modules = VehiclesModule.class) public interface VehiclesComponent { Car buildCar(); }

Remarquez comment nous avons passé notre classe de module en tant qu'argument à l' annotation @Component . Si nous ne faisions pas cela, Dagger ne saurait pas comment construire les dépendances de la voiture.

De plus, étant donné que notre module fournit un objet singleton, nous devons donner la même portée à notre composant car Dagger ne permet pas aux composants sans portée de faire référence à des liaisons étendues .

4.3. Code client

Enfin, nous pouvons lancer mvn compile afin de déclencher les processeurs d'annotation et générer le code de l'injecteur.

Après cela, nous trouverons notre implémentation de composant avec le même nom que l'interface, juste préfixée par « Dagger »:

@Test public void givenGeneratedComponent_whenBuildingCar_thenDependenciesInjected() { VehiclesComponent component = DaggerVehiclesComponent.create(); Car carOne = component.buildCar(); Car carTwo = component.buildCar(); Assert.assertNotNull(carOne); Assert.assertNotNull(carTwo); Assert.assertNotNull(carOne.getEngine()); Assert.assertNotNull(carTwo.getEngine()); Assert.assertNotNull(carOne.getBrand()); Assert.assertNotNull(carTwo.getBrand()); Assert.assertNotEquals(carOne.getEngine(), carTwo.getEngine()); Assert.assertEquals(carOne.getBrand(), carTwo.getBrand()); }

5. Analogies de printemps

Ceux qui sont familiers avec Spring ont peut-être remarqué des parallèles entre les deux frameworks.

L' annotation @Module de Dagger rend le conteneur conscient d'une classe d'une manière très similaire à toutes les annotations stéréotypées de Spring (par exemple, @Service , @Controller …). De même, @Provides et @Component sont presque équivalents aux @Bean et @Lookup de Spring respectivement.

Spring a également son annotation @Scope , en corrélation avec @Singleton , bien que notez ici déjà une autre différence en ce que Spring suppose une portée singleton par défaut tandis que Dagger utilise par défaut ce que les développeurs Spring pourraient appeler la portée prototype, en invoquant la méthode du fournisseur à chaque fois la dépendance est requise.

6. Conclusion

Dans cet article, nous avons expliqué comment configurer et utiliser Dagger 2 avec un exemple de base. Nous avons également examiné les différences entre l'injection au moment de l'exécution et au moment de la compilation.

Comme toujours, tout le code de l'article est disponible à l'adresse over sur GitHub.