Java Dependency Injection – DI Design Pattern Voorbeeld Tutorial

Java Dependency Injection ontwerppatroon stelt ons in staat om de hardgecodeerde afhankelijkheden te verwijderen en onze applicatie losjes gekoppeld, uitbreidbaar en onderhoudbaar te maken. We kunnen dependency injection in Java implementeren om de afhankelijkheidsresolutie van compiletijd naar runtime te verplaatsen.

Java Dependency Injection

Dependency Injection in Java lijkt moeilijk te begrijpen aan de hand van theorie, dus ik neem een eenvoudig voorbeeld en dan zullen we zien hoe we het patroon van dependency injection kunnen gebruiken om losse koppeling en uitbreidbaarheid in de applicatie te bereiken. Laten we zeggen dat we een applicatie hebben waarin we EmailService gebruiken om e-mails te verzenden. Normaal zouden we dit als volgt implementeren.

package com.journaldev.java.legacy;

public class EmailService {

	public void sendEmail(String message, String receiver){
		// logica om e-mail te verzenden
		System.out.println("Email sent to "+receiver+ " with Message="+message);
	}
}

De klasse EmailService bevat de logica om een e-mailbericht naar het e-mailadres van de ontvanger te sturen. Onze applicatiecode zal als volgt zijn.

package com.journaldev.java.legacy;

public class MyApplication {

	private EmailService email = new EmailService();
	
	public void processMessages(String msg, String rec){
		// voer wat berichtvalidatie, manipulatielogica enz. uit
		this.email.sendEmail(msg, rec);
	}
}

Onze clientcode die de klasse MyApplication zal gebruiken om e-mailberichten te verzenden, zal als volgt zijn.

package com.journaldev.java.legacy;

public class MyLegacyTest {

	public static void main(String[] args) {
		MyApplication app = new MyApplication();
		app.processMessages("Hi Pankaj", "[email protected]");
	}

}

Op het eerste gezicht lijkt er niets mis te zijn met de bovenstaande implementatie. Maar bovenstaande code heeft bepaalde beperkingen.

  • De MyApplication klasse is verantwoordelijk voor het initialiseren van de e-mailservice en vervolgens het gebruik ervan. Dit leidt tot een hard gecodeerde afhankelijkheid. Als we in de toekomst willen overschakelen naar een andere geavanceerde e-mailservice, zullen er codeveranderingen nodig zijn in de MyApplication klasse. Dit maakt onze toepassing moeilijk uit te breiden en als de e-mailservice in meerdere klassen wordt gebruikt, zou dat nog moeilijker zijn.
  • Als we onze toepassing willen uitbreiden om een aanvullende berichtfunctie te bieden, zoals sms of Facebook-bericht, dan zouden we een andere toepassing moeten schrijven. Dit vereist codeveranderingen in toepassingsklassen en ook in klantklassen.
  • Het testen van de toepassing zal zeer moeilijk zijn omdat onze toepassing rechtstreeks een instantie van de e-mailservice maakt. Er is geen manier waarop we deze objecten kunnen faken in onze testklassen.

Men kan redeneren dat we de creatie van de e-mailservice-instantie uit de MyApplication klasse kunnen verwijderen door een constructor te hebben die e-mailservice vereist als 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){
		//voer enkele msg-validatie, manipulatielogica etc. uit
		this.email.sendEmail(msg, rec);
	}
}

Maar in dat geval vragen we clienttoepassingen of testklassen om de e-mailservice te initialiseren, wat geen goede ontwerpbeslissing is. Laten we nu eens kijken hoe we het java-afhankelijkheidsinjectiepatroon kunnen toepassen om alle problemen met de bovenstaande implementatie op te lossen. Afhankelijkheidsinjectie in java vereist minimaal het volgende:

  1. Servicecomponenten moeten worden ontworpen met een basisklasse of interface. Het is beter om interfaces of abstracte klassen te verkiezen die het contract voor de services definiëren.
  2. Consumentklassen moeten worden geschreven in termen van service-interface.
  3. Injector klassen die de services zullen initialiseren en vervolgens de consumentenklassen.

Java Dependency Injection – Servicecomponenten

Voor ons geval kunnen we MessageService hebben dat het contract voor service-implementaties zal verklaren.

package com.journaldev.java.dependencyinjection.service;

public interface MessageService {

	void sendMessage(String msg, String rec);
}

Laten we nu zeggen dat we e-mails en sms-services hebben die de bovenstaande interfaces implementeren.

package com.journaldev.java.dependencyinjection.service;

public class EmailServiceImpl implements MessageService {

	@Override
	public void sendMessage(String msg, String rec) {
		// logica om e-mail te versturen
		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) {
		// logica om SMS te versturen
		System.out.println("SMS sent to "+rec+ " with Message="+msg);
	}

}

Onze afhankelijkheidsinjectie Java-services zijn klaar en nu kunnen we onze consumentenklasse schrijven.

Java Dependency Injection – Serviceconsument

We zijn niet verplicht om basisinterfaces voor consumentenklassen te hebben, maar ik zal een Consument interface hebben die het contract voor consumentenklassen verklaart.

package com.journaldev.java.dependencyinjection.consumer;

public interface Consumer {

	void processMessages(String msg, String rec);
}

Mijn implementatie van de consumentenklasse is als volgt.

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){
		// doe wat berichtvalidatie, manipulatielogica, enz.
		this.service.sendMessage(msg, rec);
	}

}

Merk op dat onze applicatieklasse alleen gebruik maakt van de service. Het initialiseert de service niet, wat leidt tot een betere “scheiding van zorgen“. Ook het gebruik van de service-interface stelt ons in staat om de applicatie gemakkelijk te testen door de MessageService te faken en de services op runtime in plaats van op compile-time te binden. Nu zijn we klaar om java-afhankelijkheidsinjectorklassen te schrijven die de service initialiseren en ook consumentklassen.

Java-afhankelijkheidsinjectie – Injector klassen

Laten we een interface MessageServiceInjector hebben met een methodeverklaring die de Consumer-klasse retourneert.

package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;

public interface MessageServiceInjector {

	public Consumer getConsumer();
}

Nu moeten we voor elke service injector-klassen maken zoals hieronder.

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());
	}

}

Laten we nu eens kijken hoe onze clientapplicaties de applicatie zullen gebruiken met een eenvoudig programma.

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;
		
		//Email verzenden
		injector = new EmailServiceInjector();
		app = injector.getConsumer();
		app.processMessages(msg, email);
		
		//SMS verzenden
		injector = new SMSServiceInjector();
		app = injector.getConsumer();
		app.processMessages(msg, phone);
	}

}

Zoals je kunt zien, zijn onze applicatieklassen alleen verantwoordelijk voor het gebruik van de service. Serviceklassen worden aangemaakt in injectors. Ook als we onze applicatie verder moeten uitbreiden om Facebook-berichten toe te staan, zullen we alleen serviceklassen en injector-klassen moeten schrijven. Dus de implementatie van dependency injection heeft het probleem met hardgecodeerde afhankelijkheden opgelost en ons geholpen onze applicatie flexibel en gemakkelijk uitbreidbaar te maken. Laten we nu eens kijken hoe gemakkelijk we onze applicatieklasse kunnen testen door de injector en serviceklassen te faken.

Java Dependency Injection – JUnit Test Case met Mock Injector en 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(){
		//mock de injector met anonieme klasse
		injector = new MessageServiceInjector() {
			
			@Override
			public Consumer getConsumer() {
				//mock de berichtenservice
				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;
	}

}

Zoals je kunt zien, gebruik ik anonieme klassen om de injector- en serviceklassen te faken en kan ik gemakkelijk mijn applicatiemethoden testen. Ik gebruik JUnit 4 voor de bovenstaande testklasse, zorg ervoor dat deze in het buildpad van je project staat als je de bovenstaande testklasse uitvoert. We hebben constructors gebruikt om de afhankelijkheden in de applicatieklassen in te voegen, een andere manier is om een settermethode te gebruiken om afhankelijkheden in te voegen in applicatieklassen. Voor settermethode-afhankelijkheidsinjectie wordt onze applicatieklasse geïmplementeerd zoals hieronder weergegeven.

package com.journaldev.java.dependencyinjection.consumer;

import com.journaldev.java.dependencyinjection.service.MessageService;

public class MyDIApplication implements Consumer{

	private MessageService service;
	
	public MyDIApplication(){}

	//setter-afhankelijkheidsinjectie	
	public void setService(MessageService service) {
		this.service = service;
	}

	@Override
	public void processMessages(String msg, String rec){
		//voer enkele berichtvalidatie, manipulatielogica, enz. uit
		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;
	}

}

Een van de beste voorbeelden van setter-afhankelijkheidsinjectie is Struts2 Servlet API Aware interfaces. Of je nu kiest voor constructor-gebaseerde afhankelijkheidsinjectie of op setters gebaseerde injectie is een ontwerpbeslissing en hangt af van je vereisten. Bijvoorbeeld, als mijn applicatie helemaal niet kan werken zonder de serviceklasse, zou ik de voorkeur geven aan op constructoren gebaseerde DI, anders zou ik gaan voor op setters gebaseerde DI en het alleen gebruiken wanneer het echt nodig is. Dependency Injection in Java is een manier om Inversion of control (IoC) te bereiken in onze applicatie door het binden van objecten te verplaatsen van compileertijd naar runtime. We kunnen IoC bereiken via Factory Pattern, Template Method Design Pattern, Strategy Pattern en Service Locator-patroon. Spring Dependency Injection, Google Guice en het Java EE CDI-framework vergemakkelijken het proces van afhankelijkheidsinjectie door gebruik te maken van Java Reflection API en Java-annotaties. Het enige wat we moeten doen, is het veld, de constructor of de setter-methode annoteren en deze configureren in configuratie XML-bestanden of klassen.

Voordelen van Java Dependency Injection

Sommige voordelen van het gebruik van Dependency Injection in Java zijn:

  • Scheiding van zorgen
  • Vermindering van boilerplate-code in applicatieklassen omdat al het werk om afhankelijkheden te initialiseren wordt afgehandeld door het injectiecomponent
  • Configureerbare componenten maken de applicatie eenvoudig uitbreidbaar
  • Unit testing is eenvoudig met mock objecten

Nadelen van Java Dependency Injection

Java Dependency Injection heeft ook nadelen:

  • Als het te veel wordt gebruikt, kan het leiden tot onderhoudsproblemen omdat het effect van wijzigingen pas bekend is tijdens runtime.
  • Dependency Injection in Java verbergt de serviceklasse-afhankelijkheden die kunnen leiden tot runtime-fouten die tijdens het compileren zouden zijn opgevangen.

Download Dependency Injection Project

Dat is alles voor dependency injection-patroon in Java. Het is goed om het te kennen en te gebruiken wanneer we controle hebben over de services.

Source:
https://www.digitalocean.com/community/tutorials/java-dependency-injection-design-pattern-example-tutorial