Spring-Abhängigkeitsinjektion

Heute werden wir uns mit der Spring Dependency Injection befassen. Die Kernkonzepte des Spring Frameworks sind „Dependency Injection“ und „Aspect Oriented Programming„. Ich habe früher über Java Dependency Injection geschrieben und wie wir das Google Guice Framework nutzen können, um diesen Prozess in unseren Anwendungen zu automatisieren.

Die Spring Dependency Injection

Dieses Tutorial zielt darauf ab, Details zum Beispiel der Spring-Abhängigkeitsinjektion sowohl mit konfigurationsbasierter Annotation als auch mit XML-Dateibasierter Konfiguration bereitzustellen. Ich werde auch ein JUnit-Testfallbeispiel für die Anwendung bereitstellen, da die einfache Testbarkeit eine der Hauptvorteile der Abhängigkeitsinjektion ist. Ich habe das Maven-Projekt spring-dependency-injection erstellt, dessen Struktur wie im folgenden Bild aussieht. Schauen wir uns nun jede der Komponenten einzeln an.

Spring Dependency Injection – Maven-Abhängigkeiten

I have added Spring and JUnit maven dependencies in pom.xml file, final pom.xml code is below.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.journaldev.spring</groupId>
	<artifactId>spring-dependency-injection</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.0.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

Die aktuelle stabile Version des Spring Frameworks ist 4.0.0.RELEASE und die aktuelle Version von JUnit ist 4.8.1. Wenn Sie andere Versionen verwenden, besteht möglicherweise eine geringe Chance, dass das Projekt einige Änderungen benötigt. Beim Erstellen des Projekts werden Sie feststellen, dass einige andere JAR-Dateien aufgrund transitiver Abhängigkeiten zu den Maven-Abhängigkeiten hinzugefügt wurden, ähnlich wie im obigen Bild.

Spring Dependency Injection – Service-Klassen

Angenommen, wir möchten E-Mail-Nachrichten und Twitter-Nachrichten an die Benutzer senden. Für die Dependency Injection benötigen wir eine Basisklasse für die Dienste. Also habe ich die MessageService-Schnittstelle mit einer einzigen Methodendeclaration für das Senden von Nachrichten erstellt.

package com.journaldev.spring.di.services;

public interface MessageService {

	boolean sendMessage(String msg, String rec);
}

Jetzt werden wir tatsächliche Implementierungsklassen haben, um E-Mail- und Twitter-Nachrichten zu senden.

package com.journaldev.spring.di.services;

public class EmailService implements MessageService {

	public boolean sendMessage(String msg, String rec) {
		System.out.println("Email Sent to "+rec+ " with Message="+msg);
		return true;
	}

}
package com.journaldev.spring.di.services;

public class TwitterService implements MessageService {

	public boolean sendMessage(String msg, String rec) {
		System.out.println("Twitter message Sent to "+rec+ " with Message="+msg);
		return true;
	}

}

Jetzt, da unsere Dienste bereit sind, können wir zu den Komponentenklassen übergehen, die den Dienst verbrauchen werden.

Spring Dependency Injection – Komponentenklassen

Wir schreiben eine Verbraucherklasse für die oben genannten Dienste. Wir werden zwei Verbraucherklassen haben – eine mit Spring-Annotationen für die automatische Verkabelung und eine andere ohne Annotation, wobei die Verkabelungskonfiguration in der XML-Konfigurationsdatei bereitgestellt wird.

package com.journaldev.spring.di.consumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

import com.journaldev.spring.di.services.MessageService;

@Component
public class MyApplication {

	//Feldbasierte Dependency Injection
	//@Autowired
	private MessageService service;
	
//Konstruktorbasierte Dependency Injection	
//	@Autowired
//	public MyApplication(MessageService svc){
//		this.service=svc;
//	}
	
	@Autowired
	public void setService(MessageService svc){
		this.service=svc;
	}
	
	public boolean processMessage(String msg, String rec){
		//etwas Magie wie Validierung, Protokollierung usw.
		return this.service.sendMessage(msg, rec);
	}
}

Einige wichtige Punkte zur Klasse MyApplication:

  • Die @Component-Annotation wird der Klasse hinzugefügt, damit sie bei der Suche nach Komponenten durch das Spring-Framework als Komponente behandelt wird. Die @Component-Annotation kann nur auf Klassen angewendet werden und ihre Beibehaltungspolitik ist Runtime. Wenn Sie nicht mit der Beibehaltungspolitik von Annotationen vertraut sind, empfehle ich Ihnen, das Java-Annotations-Tutorial zu lesen.
  • Die @Autowired-Annotation wird verwendet, um Spring mitzuteilen, dass eine automatische Verdrahtung erforderlich ist. Dies kann auf Felder, Konstruktoren und Methoden angewendet werden. Diese Annotation ermöglicht die Implementierung der injektionsabhängigkeit basierend auf Konstruktor, Feld oder Methode in unseren Komponenten.
  • In unserem Beispiel verwenden wir die injektionsabhängigkeit basierend auf Methoden. Sie können die Konstruktor-Methode auskommentieren, um auf die injektionsabhängigkeit basierend auf Konstruktor umzustellen.

Jetzt schreiben wir eine ähnliche Klasse ohne Annotationen.

package com.journaldev.spring.di.consumer;

import com.journaldev.spring.di.services.MessageService;

public class MyXMLApplication {

	private MessageService service;

	//injektionsabhängigkeit basierend auf Konstruktor
//	public MyXMLApplication(MessageService svc) {
//		this.service = svc;
//	}
	
	//injektionsabhängigkeit basierend auf Setter-Methode
	public void setService(MessageService svc){
		this.service=svc;
	}

	public boolean processMessage(String msg, String rec) {
		// etwas Magisches wie Validierung, Protokollierung usw.
		return this.service.sendMessage(msg, rec);
	}
}

A simple application class consuming the service. For XML based configuration, we can use implement either constructor-based spring dependency injection or method-based spring dependency injection. Note that method-based and setter-based injection approaches are same, it’s just that some prefer calling it setter-based and some call it method-based.

Spring-Abhängigkeitsinjektionskonfiguration mit Annotationen

Für die konfigurationsbasierte Annotation müssen wir eine Konfigurator-Klasse schreiben, die verwendet wird, um die tatsächliche Implementierungsbohne an die Komponenteneigenschaft zu injizieren.

package com.journaldev.spring.di.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.journaldev.spring.di.services.EmailService;
import com.journaldev.spring.di.services.MessageService;

@Configuration
@ComponentScan(value={"com.journaldev.spring.di.consumer"})
public class DIConfiguration {

	@Bean
	public MessageService getMessageService(){
		return new EmailService();
	}
}

Einige wichtige Punkte zu obiger Klasse sind:

  • @Configuration-Annotation wird verwendet, um Spring mitzuteilen, dass es sich um eine Konfigurationsklasse handelt.
  • @ComponentScan-Annotation wird zusammen mit @Configuration-Annotation verwendet, um die Pakete anzugeben, in denen nach Komponentenklassen gesucht werden soll.
  • @Bean-Annotation wird verwendet, um dem Spring-Framework mitzuteilen, dass diese Methode verwendet werden soll, um die Bean-Implementierung zu erhalten, die in Komponentenklassen injiziert werden soll.

Lassen Sie uns ein einfaches Programm schreiben, um unser Beispiel für die konfigurationsbasierte Annotation der Spring-Abhängigkeitsinjektion zu testen.

package com.journaldev.spring.di.test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.journaldev.spring.di.configuration.DIConfiguration;
import com.journaldev.spring.di.consumer.MyApplication;

public class ClientApplication {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DIConfiguration.class);
		MyApplication app = context.getBean(MyApplication.class);
		
		app.processMessage("Hi Pankaj", "[email protected]");
		
		// Schließen Sie den Kontext
		context.close();
	}

}

AnnotationConfigApplicationContext ist die Implementierung der abstrakten Klasse AbstractApplicationContext und wird verwendet, um Dienste in Komponenten zu verdrahten, wenn Annotationen verwendet werden. Der Konstruktor von AnnotationConfigApplicationContext nimmt eine Klasse als Argument, die zur Bean-Implementierung verwendet wird, um sie in Komponentenklassen einzufügen. Die Methode getBean(Class) gibt das Komponentenobjekt zurück und verwendet die Konfiguration zum Autowiring der Objekte. Kontextobjekte sind ressourcenintensiv, daher sollten wir sie schließen, wenn wir damit fertig sind. Wenn wir das obige Programm ausführen, erhalten wir folgende Ausgabe.

Dec 16, 2013 11:49:20 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3067ed13: startup date [Mon Dec 16 23:49:20 PST 2013]; root of context hierarchy
Email Sent to [email protected] with Message=Hi Pankaj
Dec 16, 2013 11:49:20 PM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3067ed13: startup date [Mon Dec 16 23:49:20 PST 2013]; root of context hierarchy

Spring Dependency Injection XML-basierte Konfiguration

Wir erstellen eine Spring-Konfigurationsdatei mit folgenden Daten, der Dateiname kann beliebig sein. applicationContext.xml Code:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

<!-- 
	<bean id="MyXMLApp" class="com.journaldev.spring.di.consumer.MyXMLApplication">
		<constructor-arg>
			<bean class="com.journaldev.spring.di.services.TwitterService" />
		</constructor-arg>
	</bean>
-->
	<bean id="twitter" class="com.journaldev.spring.di.services.TwitterService"></bean>
	<bean id="MyXMLApp" class="com.journaldev.spring.di.consumer.MyXMLApplication">
		<property name="service" ref="twitter"></property>
	</bean>
</beans>

Beachten Sie, dass die obige XML-Konfiguration sowohl die konstruktorbasierte als auch die setterbasierte Spring-Dependency-Injection enthält. Da MyXMLApplication die Setter-Methode für die Injektion verwendet, enthält die Bean-Konfiguration das property-Element für die Injektion. Für die konstruktorbasierte Injektion müssen wir das constructor-arg-Element verwenden. Die Konfigurationsdatei XML befindet sich im Quellverzeichnis, daher wird sie nach dem Build im Klassenverzeichnis sein. Schauen wir uns an, wie man die XML-basierte Konfiguration mit einem einfachen Programm verwendet.

package com.journaldev.spring.di.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.journaldev.spring.di.consumer.MyXMLApplication;

public class ClientXMLApplication {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		MyXMLApplication app = context.getBean(MyXMLApplication.class);

		app.processMessage("Hi Pankaj", "[email protected]");

		// Kontext schließen
		context.close();
	}

}

ClassPathXmlApplicationContext wird verwendet, um das ApplicationContext-Objekt durch Angabe des Konfigurationsdateiorts zu erhalten. Es hat mehrere überladene Konstruktoren, und wir können auch mehrere Konfigurationsdateien angeben. Der restliche Code ist ähnlich dem Testprogramm für die konfigurationsbasierte Annotation, der einzige Unterschied besteht darin, wie wir das ApplicationContext-Objekt je nach unserer Konfigurationswahl erhalten. Wenn wir das obige Programm ausführen, erhalten wir folgende Ausgabe.

Dec 17, 2013 12:01:23 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4eeaabad: startup date [Tue Dec 17 00:01:23 PST 2013]; root of context hierarchy
Dec 17, 2013 12:01:23 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
Twitter message Sent to [email protected] with Message=Hi Pankaj
Dec 17, 2013 12:01:23 AM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4eeaabad: startup date [Tue Dec 17 00:01:23 PST 2013]; root of context hierarchy

Beachten Sie, dass ein Teil der Ausgabe vom Spring Framework geschrieben wird. Da das Spring Framework log4j für Protokollzwecke verwendet und ich es nicht konfiguriert habe, wird die Ausgabe in die Konsole geschrieben.

Spring Dependency Injection JUnit-Testfall

Einer der wichtigsten Vorteile der Dependency Injection in Spring ist die Möglichkeit, Mock-Serviceklassen anstelle von tatsächlichen Diensten zu verwenden. Daher habe ich alles, was ich oben gelernt habe, kombiniert und alles in einer einzigen JUnit-4-Testklasse für die Dependency Injection in Spring geschrieben.

package com.journaldev.spring.di.test;

import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.journaldev.spring.di.consumer.MyApplication;
import com.journaldev.spring.di.services.MessageService;

@Configuration
@ComponentScan(value="com.journaldev.spring.di.consumer")
public class MyApplicationTest {
	
	private AnnotationConfigApplicationContext context = null;

	@Bean
	public MessageService getMessageService() {
		return new MessageService(){

			public boolean sendMessage(String msg, String rec) {
				System.out.println("Mock Service");
				return true;
			}
			
		};
	}

	@Before
	public void setUp() throws Exception {
		context = new AnnotationConfigApplicationContext(MyApplicationTest.class);
	}
	
	@After
	public void tearDown() throws Exception {
		context.close();
	}

	@Test
	public void test() {
		MyApplication app = context.getBean(MyApplication.class);
		Assert.assertTrue(app.processMessage("Hi Pankaj", "[email protected]"));
	}

}

Die Klasse ist mit der Annotation @Configuration und @ComponentScan annotiert, weil die Methode getMessageService() die Mock-Implementierung von MessageService zurückgibt. Deshalb ist getMessageService() mit der Annotation @Bean annotiert. Da ich die Klasse MyApplication teste, die mit Annotation konfiguriert ist, verwende ich AnnotationConfigApplicationContext und erstelle ihr Objekt in der setUp()-Methode. Der Kontext wird in der tearDown()-Methode geschlossen. Der Code der test()-Methode ruft einfach das Komponentenobjekt aus dem Kontext ab und testet es. Fragen Sie sich, wie das Spring Framework die Autowiring und das Aufrufen von Methoden durchführt, die dem Spring Framework unbekannt sind? Dies wird mit der umfangreichen Verwendung von Java Reflection erreicht, das wir verwenden können, um das Verhalten der Klassen zur Laufzeit zu analysieren und zu ändern.

Spring Dependency Injection Projekt herunterladen

Laden Sie das Beispielprojekt für die Dependency Injection (DI) von Spring von der obigen URL herunter und experimentieren Sie damit, um mehr zu lernen.

Source:
https://www.digitalocean.com/community/tutorials/spring-dependency-injection