Oggi esamineremo l’Iniezione di Dipendenza in Spring. I concetti fondamentali del Framework Spring sono “Iniezione di Dipendenza” e “Programmazione Orientata agli Aspetti”. Ho già scritto in precedenza su Iniezione di Dipendenza in Java e su come possiamo utilizzare il framework Google Guice per automatizzare questo processo nelle nostre applicazioni.
Iniezione di Dipendenza in Spring
Questo tutorial ha lo scopo di fornire dettagli sull’esempio di Dependency Injection di Spring con entrambe le configurazioni basate su annotazioni e basate su file XML. Fornirò anche un esempio di caso di test JUnit per l’applicazione, poiché la facilità di testabilità è uno dei principali vantaggi dell’iniezione di dipendenze. Ho creato il progetto maven spring-dependency-injection il cui struttura è simile all’immagine qui sotto.
Esaminiamo ciascuno dei componenti uno per uno.
Iniezione di dipendenze di Spring – Dipendenze Maven
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>
La versione stabile attuale del Framework Spring è 4.0.0.RELEASE e la versione attuale di JUnit è 4.8.1, se si utilizzano altre versioni potrebbe esserci una piccola possibilità che il progetto richieda qualche modifica. Se si costruisce il progetto, si noterà che vengono aggiunti anche altri jar alle dipendenze maven a causa delle dipendenze trasitive, proprio come nell’immagine sopra.
Iniezione di Dipendenza di Spring – Classi di Servizio
Diciamo che vogliamo inviare un messaggio email e un messaggio Twitter agli utenti. Per l’iniezione di dipendenza, dobbiamo avere una classe di base per i servizi. Ho quindi un’interfaccia MessageService
con una dichiarazione di metodo singolo per l’invio del messaggio.
package com.journaldev.spring.di.services;
public interface MessageService {
boolean sendMessage(String msg, String rec);
}
Ora avremo classi di implementazione effettive per inviare il messaggio email e Twitter.
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;
}
}
Ora che i nostri servizi sono pronti, possiamo passare alle classi Component che consumeranno il servizio.
Iniezione di Dipendenza di Spring – Classi Component
Scriviamo una classe consumer per i servizi sopra. Avremo due classi consumer: una con annotazioni Spring per l’autowiring e un’altra senza annotazioni e la configurazione del cablaggio sarà fornita nel file di configurazione XML.
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 {
//Iniezione di dipendenza basata su campo
//@Autowired
private MessageService service;
//Iniezione di dipendenza basata su costruttore
//@Autowired
//public MyApplication(MessageService svc){
// this.service=svc;
// }
@Autowired
public void setService(MessageService svc){
this.service=svc;
}
public boolean processMessage(String msg, String rec){
//qualche magia come validazione, logging, ecc.
return this.service.sendMessage(msg, rec);
}
}
Alcuni punti importanti sulla classe MyApplication:
@Component
annotazione viene aggiunta alla classe in modo che, quando il framework Spring scannerà i componenti, questa classe verrà trattata come un componente. @Component annotazione può essere applicata solo alla classe e la sua politica di conservazione è Runtime. Se non sei familiare con la politica di conservazione delle annotazioni, ti consiglio di leggere il tutorial sulle annotazioni Java.@Autowired
annotazione viene utilizzata per far sapere a Spring che l’autowiring è richiesto. Questa può essere applicata a campi, costruttori e metodi. Questa annotazione ci consente di implementare l’iniezione di dipendenza basata su costruttore, su campo o su metodo nei nostri componenti.- Per il nostro esempio, sto utilizzando l’iniezione di dipendenza basata su metodo. Puoi togliere il commento dal metodo del costruttore per passare all’iniezione di dipendenza basata su costruttore.
Ora scriviamo una classe simile senza annotazioni.
package com.journaldev.spring.di.consumer;
import com.journaldev.spring.di.services.MessageService;
public class MyXMLApplication {
private MessageService service;
// Iniezione di dipendenza basata su costruttore
// public MyXMLApplication(MessageService svc) {
// this.service = svc;
// }
// Iniezione di dipendenza basata su setter
public void setService(MessageService svc){
this.service=svc;
}
public boolean processMessage(String msg, String rec) {
// qualche magia come la validazione, il logging, ecc.
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.
Configurazione dell’Iniezione di Dipendenza di Spring con le Annotazioni
Per la configurazione basata su annotazioni, è necessario scrivere una classe Configurator che verrà utilizzata per iniettare l’implementazione effettiva del bean nella proprietà del componente.
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();
}
}
Alcuni punti importanti relativi alla classe sopra menzionata sono:
@Configuration
viene utilizzata per far sapere a Spring che si tratta di una classe di configurazione.- È possibile utilizzare l’annotazione
@ComponentScan
insieme all’annotazione@Configuration
per specificare i pacchetti in cui cercare le classi dei componenti. - L’annotazione
@Bean
viene utilizzata per far sapere al framework Spring che questo metodo dovrebbe essere utilizzato per ottenere l’implementazione del bean da iniettare nelle classi dei componenti.
Scriviamo un programma semplice per testare il nostro esempio di Iniezione di Dipendenza di Spring basata su annotazioni.
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]");
//chiudi il contesto
context.close();
}
}
AnnotationConfigApplicationContext
è l’implementazione della classe astratta AbstractApplicationContext
ed è utilizzata per l’autowiring dei servizi ai componenti quando vengono utilizzate le annotazioni. Il costruttore di AnnotationConfigApplicationContext
prende come argomento una classe che verrà utilizzata per ottenere l’implementazione del bean da iniettare nelle classi dei componenti. Il metodo getBean(Class) restituisce l’oggetto Component e utilizza la configurazione per l’autowiring degli oggetti. Gli oggetti di contesto sono intensivi in termini di risorse, quindi dovremmo chiuderli quando abbiamo finito con essi. Quando eseguiamo il programma sopra, otteniamo l’output seguente.
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
Configurazione basata su XML per l’iniezione delle dipendenze di Spring
Crea un file di configurazione di Spring con i seguenti dati, il nome del file può essere qualsiasi cosa. Codice applicationContext.xml:
<?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>
Si noti che l’XML precedente contiene la configurazione sia per l’iniezione delle dipendenze di Spring basata sul costruttore che basata sul setter. Poiché MyXMLApplication
utilizza il metodo setter per l’iniezione, la configurazione del bean contiene l’elemento property per l’iniezione. Per l’iniezione basata sul costruttore, dobbiamo utilizzare l’elemento constructor-arg. Il file di configurazione XML è posizionato nella directory delle risorse, quindi sarà nella directory delle classi dopo la compilazione. Vediamo come utilizzare la configurazione basata su XML con un semplice programma.
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]");
// chiudi il contesto
context.close();
}
}
ClassPathXmlApplicationContext
viene utilizzato per ottenere l’oggetto ApplicationContext fornendo la posizione dei file di configurazione. Ha diversi costruttori sovraccaricati e possiamo fornire anche più file di configurazione. Il resto del codice è simile al programma di test della configurazione basata su annotazioni, l’unica differenza è il modo in cui otteniamo l’oggetto ApplicationContext in base alla nostra scelta di configurazione. Quando eseguiamo il programma sopra, otteniamo l’output seguente.
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
Si noti che parte dell’output è scritta dal framework Spring. Poiché il framework Spring utilizza log4j a scopo di registrazione e non l’ho configurato, l’output viene scritto sulla console.
Test Case JUnit per l’Injection delle Dipendenze di Spring
Uno dei principali vantaggi dell’iniezione delle dipendenze in Spring è la facilità di avere classi di servizio mock invece di utilizzare servizi effettivi. Ho quindi combinato tutto ciò che ho imparato sopra e ho scritto tutto in una singola classe di test JUnit 4 per l’iniezione delle dipendenze in Spring.
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]"));
}
}
Il codice è annotato con @Configuration
e @ComponentScan
perché il metodo getMessageService() restituisce l’implementazione mock di MessageService
. Ecco perché getMessageService() è annotato con @Bean
. Poiché sto testando la classe MyApplication
configurata con l’annotazione, utilizzo AnnotationConfigApplicationContext
e ne creo l’oggetto nel metodo setUp(). Il contesto viene chiuso nel metodo tearDown(). Il codice del metodo test() ottiene semplicemente l’oggetto del componente dal contesto e lo testa. Ti chiedi come il framework Spring faccia l’autowiring e chiami i metodi sconosciuti al framework Spring? Questo avviene attraverso un ampio utilizzo di Java Reflection, che possiamo utilizzare per analizzare e modificare i comportamenti delle classi a runtime.
Scarica il progetto di Dependency Injection di Spring
Scarica il progetto di Dependency Injection (DI) di Spring dal URL sopra e gioca con esso per imparare di più.
Source:
https://www.digitalocean.com/community/tutorials/spring-dependency-injection