Le modèle de conception Injection de Dépendance Java nous permet de supprimer les dépendances codées en dur et de rendre notre application faiblement couplée, extensible et maintenable. Nous pouvons implémenter l’injection de dépendance en Java pour déplacer la résolution des dépendances de la phase de compilation à celle de l’exécution.
L’injection de dépendance Java
L’injection de dépendance Java semble difficile à comprendre en théorie, donc je vais prendre un exemple simple et ensuite nous verrons comment utiliser le modèle d’injection de dépendance pour obtenir un couplage lâche et une extensibilité dans l’application. Disons que nous avons une application où nous consommons le ServiceEmail
pour envoyer des e-mails. Normalement, nous implémenterions cela comme ci-dessous.
package com.journaldev.java.legacy;
public class EmailService {
public void sendEmail(String message, String receiver){
// logique pour envoyer un e-mail
System.out.println("Email sent to "+receiver+ " with Message="+message);
}
}
La classe ServiceEmail
contient la logique pour envoyer un message électronique à l’adresse e-mail du destinataire. Notre code d’application sera comme ci-dessous.
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = new EmailService();
public void processMessages(String msg, String rec){
// faire une certaine validation de message, manipulation de logique, etc
this.email.sendEmail(msg, rec);
}
}
Notre code client qui utilisera la classe MonApplication
pour envoyer des messages électroniques sera comme ci-dessous.
package com.journaldev.java.legacy;
public class MyLegacyTest {
public static void main(String[] args) {
MyApplication app = new MyApplication();
app.processMessages("Hi Pankaj", "[email protected]");
}
}
À première vue, il ne semble rien de mal avec la mise en œuvre ci-dessus. Mais la logique du code ci-dessus a certaines limitations.
- La classe
MyApplication
est responsable d’initialiser le service de messagerie électronique, puis de l’utiliser. Cela entraîne une dépendance codée en dur. Si nous voulons passer à un autre service de messagerie électronique plus avancé à l’avenir, cela nécessitera des modifications de code dans la classe MyApplication. Cela rend notre application difficile à étendre et si le service de messagerie électronique est utilisé dans plusieurs classes, cela serait encore plus difficile. - Si nous voulons étendre notre application pour fournir une fonctionnalité de messagerie supplémentaire, telle que les SMS ou les messages Facebook, alors nous aurions besoin d’écrire une autre application pour cela. Cela impliquera des modifications de code dans les classes d’application et dans les classes clientes également.
- Tester l’application sera très difficile car notre application crée directement l’instance du service de messagerie électronique. Il n’y a aucun moyen de simuler ces objets dans nos classes de test.
On pourrait soutenir que nous pouvons supprimer la création de l’instance du service de messagerie électronique de la classe MyApplication
en ayant un constructeur qui demande le service de messagerie électronique comme argument.
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = null;
public MyApplication(EmailService svc){
this.email=svc;
}
public void processMessages(String msg, String rec){
//faire une validation des messages, de la logique de manipulation, etc.
this.email.sendEmail(msg, rec);
}
}
Mais dans ce cas, nous demandons aux applications clientes ou aux classes de test d’initialiser le service de messagerie électronique, ce qui n’est pas une bonne décision de conception. Voyons maintenant comment nous pouvons appliquer le modèle d’injection de dépendances Java pour résoudre tous les problèmes avec l’implémentation ci-dessus. L’injection de dépendances en Java nécessite au moins ce qui suit :
- Les composants de service doivent être conçus avec une classe de base ou une interface. Il est préférable de privilégier les interfaces ou les classes abstraites qui définiraient le contrat pour les services.
- Les classes consommatrices doivent être écrites en termes d’interface de service.
- Les classes d’injection qui initialiseront les services, puis les classes de consommation.
Injection de dépendance Java – Composants de service
Pour notre cas, nous pouvons avoir MessageService
qui déclarera le contrat pour les implémentations de service.
package com.journaldev.java.dependencyinjection.service;
public interface MessageService {
void sendMessage(String msg, String rec);
}
Maintenant, supposons que nous ayons des services de messagerie électronique et SMS qui implémentent les interfaces ci-dessus.
package com.journaldev.java.dependencyinjection.service;
public class EmailServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
//logique d'envoi d'e-mail
System.out.println("Email sent to "+rec+ " with Message="+msg);
}
}
package com.journaldev.java.dependencyinjection.service;
public class SMSServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
//logique d'envoi de SMS
System.out.println("SMS sent to "+rec+ " with Message="+msg);
}
}
Nos services d’injection de dépendances en Java sont prêts et maintenant nous pouvons écrire notre classe consommatrice.
Injection de dépendance Java – Consommateur de services
Il n’est pas nécessaire d’avoir des interfaces de base pour les classes consommatrices, mais j’aurai une interface Consumer
déclarant le contrat pour les classes consommatrices.
package com.journaldev.java.dependencyinjection.consumer;
public interface Consumer {
void processMessages(String msg, String rec);
}
L’implémentation de ma classe consommatrice est la suivante.
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(MessageService svc){
this.service=svc;
}
@Override
public void processMessages(String msg, String rec){
//effectuer une validation du message, une logique de manipulation, etc.
this.service.sendMessage(msg, rec);
}
}
Remarquez que notre classe d’application se contente d’utiliser le service. Elle n’initialise pas le service, ce qui conduit à une meilleure « séparation des préoccupations« . L’utilisation de l’interface de service nous permet également de tester facilement l’application en simulant le MessageService et de lier les services au moment de l’exécution plutôt qu’au moment de la compilation. Maintenant, nous sommes prêts à écrire les classes d’injection de dépendances Java qui initialiseront le service ainsi que les classes de consommateur.
Injection de Dépendances Java – Classes d’Injecteurs
Créons une interface MessageServiceInjector
avec une déclaration de méthode qui renvoie la classe Consumer
.
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
public interface MessageServiceInjector {
public Consumer getConsumer();
}
Maintenant, pour chaque service, nous devrons créer des classes d’injecteurs comme ci-dessous.
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new EmailServiceImpl());
}
}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.SMSServiceImpl;
public class SMSServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new SMSServiceImpl());
}
}
Voyons maintenant comment nos applications clientes utiliseront l’application avec un programme simple.
package com.journaldev.java.dependencyinjection.test;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.injector.EmailServiceInjector;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.injector.SMSServiceInjector;
public class MyMessageDITest {
public static void main(String[] args) {
String msg = "Hi Pankaj";
String email = "[email protected]";
String phone = "4088888888";
MessageServiceInjector injector = null;
Consumer app = null;
//Envoyer un email
injector = new EmailServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, email);
//Envoyer un SMS
injector = new SMSServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, phone);
}
}
Comme vous pouvez le voir, nos classes d’application sont responsables uniquement de l’utilisation du service. Les classes de service sont créées dans les injecteurs. De plus, si nous devons étendre davantage notre application pour permettre l’envoi de messages sur Facebook, nous devrons écrire uniquement des classes de service et des classes d’injecteurs. Ainsi, l’implémentation de l’injection de dépendances résout le problème de la dépendance codée en dur et nous aide à rendre notre application flexible et facile à étendre. Voyons maintenant comment nous pouvons facilement tester notre classe d’application en simulant les injecteurs et les classes de service.
Injection de dépendances Java – Cas de test JUnit avec simulateur d’injection et de service
package com.journaldev.java.dependencyinjection.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplicationJUnitTest {
private MessageServiceInjector injector;
@Before
public void setUp(){
//simuler l'injecteur avec une classe anonyme
injector = new MessageServiceInjector() {
@Override
public Consumer getConsumer() {
//simuler le service de messages
return new MyDIApplication(new MessageService() {
@Override
public void sendMessage(String msg, String rec) {
System.out.println("Mock Message Service implementation");
}
});
}
};
}
@Test
public void test() {
Consumer consumer = injector.getConsumer();
consumer.processMessages("Hi Pankaj", "[email protected]");
}
@After
public void tear(){
injector = null;
}
}
Comme vous pouvez le voir, j’utilise des classes anonymes pour simuler les classes d’injecteur et de service et je peux facilement tester les méthodes de mon application. J’utilise JUnit 4 pour la classe de test ci-dessus, donc assurez-vous qu’il est dans le chemin de construction de votre projet si vous exécutez la classe de test ci-dessus. Nous avons utilisé des constructeurs pour injecter les dépendances dans les classes de l’application, une autre façon est d’utiliser une méthode setter pour injecter des dépendances dans les classes de l’application. Pour l’injection de dépendances par méthode setter, notre classe d’application sera implémentée comme suit.
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(){}
//injection de dépendances par méthode setter
public void setService(MessageService service) {
this.service = service;
}
@Override
public void processMessages(String msg, String rec){
//effectuer une validation de message, une logique de manipulation, etc.
this.service.sendMessage(msg, rec);
}
}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
MyDIApplication app = new MyDIApplication();
app.setService(new EmailServiceImpl());
return app;
}
}
L’un des meilleurs exemples d’injection de dépendance par setter est les interfaces Struts2 Servlet API Aware. Que ce soit l’injection de dépendance basée sur le constructeur ou celle basée sur les setters est une décision de conception et dépend de vos besoins. Par exemple, si mon application ne peut pas fonctionner du tout sans la classe de service, je préférerais une injection de dépendance basée sur le constructeur, sinon j’opterais pour une injection basée sur la méthode setter pour l’utiliser uniquement lorsque cela est vraiment nécessaire. L’injection de dépendance en Java est une manière d’atteindre le renversement de contrôle (IoC) dans notre application en déplaçant la liaison des objets du temps de compilation au temps d’exécution. Nous pouvons réaliser l’IoC à travers le modèle de fabrique, le modèle de méthode de modèle, le modèle de stratégie et le modèle de localisateur de service aussi. Les frameworks Spring Dependency Injection, Google Guice et Java EE CDI facilitent le processus d’injection de dépendance grâce à l’utilisation de l’API de réflexion Java et des annotations Java. Tout ce dont nous avons besoin est d’annoter le champ, le constructeur ou la méthode setter et de les configurer dans des fichiers XML de configuration ou des classes.
Avantages de l’injection de dépendance en Java
Certains des avantages de l’utilisation de l’injection de dépendance en Java sont les suivants :
- Séparation des préoccupations
- Réduction du code redondant dans les classes d’application car tout le travail d’initialisation des dépendances est géré par le composant injecteur
- Les composants configurables rendent l’application facilement extensible
- Les tests unitaires sont faciles avec des objets factices
Inconvénients de l’injection de dépendance en Java
L’injection de dépendance en Java présente également certains inconvénients :
- Si elle est utilisée de manière excessive, elle peut entraîner des problèmes de maintenance car les effets des modifications sont connus à l’exécution.
- L’injection de dépendance en Java masque les dépendances de la classe de service, ce qui peut entraîner des erreurs à l’exécution qui auraient été détectées à la compilation.
Télécharger le projet d’injection de dépendance
C’est tout pour le modèle d’injection de dépendance en Java. C’est bon de le connaître et de l’utiliser lorsque nous contrôlons les services.