Vandaag gaan we kijken naar Spring Dependency Injection. De kernconcepten van het Spring Framework zijn “Dependency Injection” en “Aspect Oriented Programming“. Ik heb eerder geschreven over Java Dependency Injection en hoe we Google Guice kunnen gebruiken om dit proces te automatiseren in onze applicaties.
Spring Dependency Injection
Deze tutorial is bedoeld om details te verstrekken over een voorbeeld van Spring Dependency Injection met zowel op annotaties gebaseerde configuratie als op XML-bestandsconfiguratie. Ik zal ook een voorbeeld van een JUnit-testgeval voor de applicatie geven, aangezien eenvoudige testbaarheid een van de belangrijkste voordelen van dependency injection is. Ik heb een spring-dependency-injection mavenproject gemaakt waarvan de structuur eruitziet zoals in onderstaande afbeelding.
Laten we elk van de componenten één voor één bekijken.
Spring Dependency Injection – Maven-afhankelijkheden
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>
De huidige stabiele versie van Spring Framework is 4.0.0.RELEASE en de huidige versie van JUnit is 4.8.1, als u andere versies gebruikt, bestaat er een kleine kans dat het project enkele aanpassingen nodig heeft. Als u het project bouwt, zult u merken dat er vanwege transitieve afhankelijkheden enkele andere jars aan de maven-afhankelijkheden zijn toegevoegd, net zoals in bovenstaande afbeelding.
Spring Dependency Injection – Serviceklassen
Stel dat we een e-mailbericht en een Twitter-bericht naar de gebruikers willen sturen. Voor dependency injection moeten we een basisklasse hebben voor de services. Dus ik heb een MessageService
-interface met een enkele methodeverklaring voor het verzenden van berichten.
package com.journaldev.spring.di.services;
public interface MessageService {
boolean sendMessage(String msg, String rec);
}
Nu zullen we daadwerkelijke implementatieklassen hebben om e-mail- en Twitter-berichten te verzenden.
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;
}
}
Nu onze services gereed zijn, kunnen we doorgaan naar Componentklassen die de service zullen consumeren.
Spring Dependency Injection – Componentklassen
Laten we een consumentenklasse schrijven voor de bovenstaande services. We zullen twee consumentenklassen hebben – één met Spring-annotaties voor autowiring en een andere zonder annotatie, waarbij de bedradingsconfiguratie wordt geleverd in het XML-configuratiebestand.
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 {
// Afhankelijkheidsinjectie op basis van velden
//@Autowired
private MessageService service;
// Afhankelijkheidsinjectie op basis van constructeurs
//@Autowired
// public MyApplication(MessageService svc){
// this.service=svc;
// }
@Autowired
public void setService(MessageService svc){
this.service=svc;
}
public boolean processMessage(String msg, String rec){
// enkele magie zoals validatie, logging etc
return this.service.sendMessage(msg, rec);
}
}
Enkele belangrijke punten over de klasse MyApplication:
@Component
annotatie wordt toegevoegd aan de klasse, zodat wanneer het Spring-framework zoekt naar de componenten, deze klasse als een component wordt behandeld. @Component annotatie kan alleen worden toegepast op de klasse en de retentiebeleid is Runtime. Als je niet bekend bent met de retentiebeleid van annotaties, raad ik je aan om java annotations tutorial te lezen.@Autowired
annotatie wordt gebruikt om Spring te laten weten dat autowiring vereist is. Dit kan worden toegepast op velden, constructors en methoden. Deze annotatie stelt ons in staat om constructor-gebaseerde, veld-gebaseerde of methode-gebaseerde dependency injection te implementeren in onze componenten.- Voor ons voorbeeld gebruik ik methode-gebaseerde dependency injection. Je kunt de constructor-methode uitcommentariëren om over te schakelen naar constructor-gebaseerde dependency injection.
Latenn we nu een vergelijkbare klasse schrijven zonder annotaties.
package com.journaldev.spring.di.consumer;
import com.journaldev.spring.di.services.MessageService;
public class MyXMLApplication {
private MessageService service;
//constructor-gebaseerde dependency injection
// public MyXMLApplication(MessageService svc) {
// this.service = svc;
// }
//setter-gebaseerde dependency injection
public void setService(MessageService svc){
this.service=svc;
}
public boolean processMessage(String msg, String rec) {
// wat magie zoals validatie, logging, etc.
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 Dependency Injection Configuratie met Annotations
Voor op annotaties gebaseerde configuratie moeten we een Configurator-klasse schrijven die zal worden gebruikt om de werkelijke implementatiebean in te voegen in de componenteigenschap.
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();
}
}
Enkele belangrijke punten met betrekking tot bovenstaande klasse zijn:
@Configuration
-annotatie wordt gebruikt om Spring te laten weten dat het een configuratieklasse is.@ComponentScan
-annotatie wordt gebruikt met@Configuration
-annotatie om de pakketten op te geven waarin naar Component-klassen moet worden gezocht.@Bean
-annotatie wordt gebruikt om Spring Framework te laten weten dat deze methode moet worden gebruikt om de bean-implementatie op te halen die moet worden ingevoegd in Component-klassen.
Laten we een eenvoudig programma schrijven om ons voorbeeld van op annotaties gebaseerde Spring Dependency Injection te 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]");
// sluit de context
context.close();
}
}
AnnotationConfigApplicationContext
is de implementatie van de abstracte klasse AbstractApplicationContext
en wordt gebruikt voor het automatisch bedraden van diensten naar componenten wanneer annotaties worden gebruikt. De constructor van AnnotationConfigApplicationContext
neemt een klasse als argument die zal worden gebruikt om de bean-implementatie op te halen en in componentklassen in te voegen. De methode getBean(Class) retourneert het Component-object en gebruikt de configuratie voor het automatisch bedraden van de objecten. Contextobjecten vereisen veel bronnen, dus we zouden ze moeten sluiten wanneer we klaar zijn met gebruiken. Wanneer we het bovenstaande programma uitvoeren, krijgen we de onderstaande uitvoer.
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 Based Configuration
We zullen een Spring-configuratiebestand maken met de onderstaande gegevens, de bestandsnaam kan van alles zijn. 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>
Merk op dat de bovenstaande XML configuratie bevat voor zowel op constructeur-gebaseerde als op setter-gebaseerde Spring Dependency Injection. Aangezien MyXMLApplication
de setter-methode gebruikt voor injectie, bevat de bean-configuratie het property-element voor injectie. Voor op constructeur-gebaseerde injectie moeten we het constructor-arg-element gebruiken. Het configuratie XML-bestand wordt in de bronmap geplaatst, zodat het zich in de classes-directory bevindt na het bouwen. Laten we nu zien hoe we XML-gebaseerde configuratie kunnen gebruiken met een eenvoudig 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]");
// sluit de context
context.close();
}
}
De ClassPathXmlApplicationContext
wordt gebruikt om het ApplicationContext-object te verkrijgen door de locatie van de configuratiebestanden op te geven. Het heeft meerdere overloaded constructors en we kunnen ook meerdere configuratiebestanden opgeven. De rest van de code is vergelijkbaar met een programma voor geteste annotatiegebaseerde configuratie, het enige verschil is de manier waarop we het ApplicationContext-object krijgen op basis van onze configuratiekeuze. Wanneer we het bovenstaande programma uitvoeren, krijgen we de volgende output.
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
Let op dat een deel van de output wordt geschreven door het Spring Framework. Aangezien het Spring Framework log4j gebruikt voor het loggen en ik het niet heb geconfigureerd, wordt de output naar de console geschreven.
Spring Dependency Injection JUnit-testgeval
Een van de belangrijkste voordelen van dependency injection in Spring is het gemak van het hebben van mock-serviceklassen in plaats van het gebruik van daadwerkelijke services. Dus heb ik alles wat ik hierboven heb geleerd gecombineerd en alles geschreven in een enkele JUnit 4-testklasse voor dependency injection 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]"));
}
}
De klas is geannoteerd met de @Configuration
en @ComponentScan
annotatie omdat de getMessageService() methode de MessageService
nep-implementatie retourneert. Daarom is getMessageService() geannoteerd met de @Bean
annotatie. Omdat ik de MyApplication
klas test die geconfigureerd is met annotatie, gebruik ik de AnnotationConfigApplicationContext
en creëer ik het object ervan in de setUp() methode. De context wordt gesloten in de tearDown() methode. De code van de test() methode haalt gewoon het componentobject op uit de context en test het. Vraag je je af hoe het Spring Framework de autowiring doet en de methoden aanroept die onbekend zijn voor het Spring Framework? Dit wordt gedaan met het zware gebruik van Java Reflection dat we kunnen gebruiken om de gedragingen van de klassen tijdens runtime te analyseren en aan te passen.
Download Spring Dependency Injection Project
Download het voorbeeld Spring Dependency Injection (DI) project van bovenstaande URL en experimenteer ermee om meer te leren.
Source:
https://www.digitalocean.com/community/tutorials/spring-dependency-injection