Au cours de cette 4ème édition de Devoxx France, j’ai pu assister à la présentation « Du 100 % pur guice » de Pauline Logna. Guice est un framework léger d’injection de dépendances, distribué par Google. Cette présentation était l’occasion de découvrir ce framework et de m’en faire une idée par rapport à Spring.
Pauline Logna est une développeuse et membre de Duchess France. Elle est également enseignante à l’université Marne-La-Vallée. Elle a eu l’occasion de travailler sur ce framework lors d’un développement d’une application backend RESTful.
Code sans Google Guice
La première partie de la présentation consistait à démontrer par des exemples de code l’intérêt d’utiliser l’injection de dépendances et les raisons de se débarrasser des “new” et des Factory.
Instanciation par un appel au constructeur
Il s’agit d’utiliser des “new” pour instancier des objets directement dans le code. Le code ci-dessous instancie l’objet CreditCardProcessor
dans la méthode du service et à chaque appel de celle-ci. Cette solution est rapide à développer mais possède plusieurs problèmes.
public class RealBillingService implements BillingService {
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
CreditCardProcessor processor = new PaypalCreditCardProcessor();
processor.charge(creditCard, order.getAmount());
}
}
Cette pratique d’instanciation entraîne des problèmes de testabilité et de modularité. Il n’y a pas de possibilité de mocker facilement les dépendances. On se retrouve également avec un couplage fort, nous avons une dépendance directe d’une classe vers une autre.
Instanciation avec le Pattern Factory
Cette fois-ci, notre classe service contient une propriété de type CreditCardProcessor. C’est le constructeur qui instancie la dépendance à travers l’utilisation du Pattern Factory. Nous remplaçons le “new” par l’appel à la Factory dans le code de notre classe.
public class RealBillingService implements BillingService {
private CreditCardProcessor processor;
public RealBillingService() {
this.processor = CreditCardProcessorFactory.getInstance();
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { ... }
}
L’instanciation se fait dans le code de la Factory.
public class CreditCardProcessorFactory {
private static CreditCardProcessor instance;
public static CreditCardProcessor getInstance() {
if (instance == null) {
return new PaypalCreditCardProcessor();
}
return instance;
}
}
Cette solution n’est pas optimale parce qu’elle est liée à un contexte statique. Les tests unitaires n’aiment pas les contextes statiques, c’est plus difficile à mocker. La solution est d’utiliser une librairie spécialisée telle que PowerMock.
Il y a un autre problème avec ce pattern, la factory est difficile à maintenir à mesure que le code de l’application augmente. La factory peut devenir rapidement un “gros plat de spaghetti”.
Le framework Guice
Google Guice est un framework léger d’injection de dépendances distribué par Google. Nous allons voir les concepts de base de Guice.
Déclarer une dépendance
Quand on a une classe qui a une référence vers une autre classe, on crée une dépendance vers cette classe. Le framework Guice va créer la dépendance entre les classes grâce à l’inversion de contrôle.
Pour déclarer cette dépendance, on utilise une annotation @com.google.inject.Inject
dans notre classe. Cette annotation va utiliser l’injecteur de Guice pour créer l’instance de la dépendance et l’injecter dans la classe.
L’injection peut se faire par constructeur, par setter ou par variable d’instance. L’exemple ci-dessous réalise une injection par constructeur, avec l’avantage de déclarer la variable CreditCardProcessor
comme final.
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
@Inject
public RealBillingService(CreditCardProcessor processor) {
this.processor = processor;
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { ... }
}
Google préconise d’utiliser l’injection par constructeur. Guice est compatible avec la JSR 330, il est possible d’utiliser l’annotation @javax.inject.Inject
.
Lier une instance à un type
Guice s’aide d’un fichier de configuration appelé Module. Un module est une classe Java qui étend la classe AbstractModule
ou implémente l’interface Module
. La méthode configure()
permet de lier une instance à un type.
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
//bind(CreditCardProcessor.class).to(VisaCreditCardProcessor.class);
bind(BillingService.class).to(RealBillingService.class);
}
}
Comme vous pouvez le voir, on associe une implémentation particulière à une interface. Par exemple, il est possible simplement de changer l’implémentation de CreditCardProcessor
. La configuration est séparée du code Java de notre service.
Dans le cas où il y a plusieurs instances possibles pour un type donné, on peut nommer ces différentes instances pour les différencier avec la méthode annotatedWith
.
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(CreditCardProcessor.class)
.annotatedWith(Names.named("Paypal"))
.to(PaypalCreditCardProcessor.class);
bind(CreditCardProcessor.class)
.annotatedWith(Names.named("Visa"))
.to(VisaCreditCardProcessor.class);
}
}
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
@Inject
public RealBillingService(@Named("Paypal") CreditCardProcessor processor) {
this.processor = processor;
}
}
Obtenir une instance
Le framework fournit la classe com.google.inject.Injector
pour obtenir une instance d’un service. Il s’agit du composant qui fabrique notre graphe d’objets et initialise le contexte de l’application.
Premièrement, nous créons l’injector en lui spécifiant un ou plusieurs modules. Ensuite, nous pouvons l’utiliser pour construire et obtenir une instance de BillingService
. Ce code est utilisable directement dans une méthode main
.
public class Application {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);
...
}
}
Il est tout à fait possible d’utiliser Google Guice dans un contexte web soit en déclarant les modules dans le web.xml soit en utilisant l’extension GuiceServlet.
Pour finir
Guice est plutôt simple à prendre en main lorsqu’on a l’habitude d’utiliser des frameworks tels que Spring ou le standard CDI (JSR 299). Cette présentation m’a permis d’aborder les concepts de base du framework. Avec les annotations de la JSR 330, certains aspects comme la déclaration des dépendances sont très proches du framework Spring.
Il s’agit d’une alternative intéressante à prendre en compte. Il a l’avantage d’être un framework léger pour l’injection de dépendances, il se concentre sur cet aspect. Spring est un framework beaucoup plus complet qui contient des briques qui vont agir sur toutes les couches de l’application.
La documentation est disponible à l’adresse https://github.com/google/guice