Inyección de Dependencias de Spring

Hoy nos adentraremos en la Inyección de Dependencias de Spring. Los conceptos clave del Marco de Spring son la “Inyección de Dependencias” y la “Programación Orientada a Aspectos”. Anteriormente, he escrito sobre la Inyección de Dependencias en Java y cómo podemos utilizar el marco de Google Guice para automatizar este proceso en nuestras aplicaciones.

Inyección de Dependencias de Spring

Este tutorial tiene como objetivo proporcionar detalles sobre el ejemplo de Inyección de Dependencias en Spring con configuración basada en anotaciones y configuración basada en archivos XML. También proporcionaré un ejemplo de caso de prueba JUnit para la aplicación, ya que la facilidad de prueba es uno de los principales beneficios de la inyección de dependencias. He creado el proyecto Maven spring-dependency-injection, cuya estructura se asemeja a la imagen que se muestra a continuación. Vamos a analizar cada uno de los componentes uno por uno.

Inyección de Dependencias en Spring – Dependencias 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 versión estable actual del Framework Spring es 4.0.0.RELEASE y la versión actual de JUnit es 4.8.1. Si estás utilizando otras versiones, existe la posibilidad de que el proyecto requiera algunos cambios. Al construir el proyecto, notarás que se añaden otras dependencias de Maven debido a dependencias transitivas, similar a la imagen anterior.

Inyección de Dependencias de Spring – Clases de Servicio

Digamos que queremos enviar un mensaje de correo electrónico y un mensaje de Twitter a los usuarios. Para la inyección de dependencias, necesitamos tener una clase base para los servicios. Así que tengo la interfaz MessageService con la declaración de un único método para enviar mensajes.

package com.journaldev.spring.di.services;

public interface MessageService {

	boolean sendMessage(String msg, String rec);
}

Ahora tendremos clases de implementación reales para enviar correo electrónico y mensajes de 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;
	}

}

Ahora que nuestros servicios están listos, podemos pasar a las clases de Componentes que consumirán el servicio.

Inyección de Dependencias de Spring – Clases de Componente

Escribamos una clase consumidora para los servicios anteriores. Tendremos dos clases consumidoras: una con anotaciones de Spring para la autowiring y otra sin anotación y la configuración de cableado se proporcionará en el archivo de configuración 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 {

	// Inyección de dependencias basada en campos
	//@Autowired
	private MessageService service;
	
// Inyección de dependencias basada en constructor	
//	@Autowired
//	public MyApplication(MessageService svc){
//		this.service=svc;
//	}
	
	@Autowired
	public void setService(MessageService svc){
		this.service=svc;
	}
	
	public boolean processMessage(String msg, String rec){
		//algo de magia como validación, registro, etc.
		return this.service.sendMessage(msg, rec);
	}
}

Algunos puntos importantes sobre la clase MyApplication:

  • La anotación @Component se agrega a la clase para que cuando el framework Spring escanee los componentes, esta clase se trate como un componente. La anotación @Component solo se puede aplicar a la clase y su política de retención es Runtime. Si no estás familiarizado con la política de retención de anotaciones, te sugiero que leas java annotations tutorial.
  • La anotación @Autowired se utiliza para informar a Spring que se requiere la autowiring. Esto se puede aplicar a campos, constructores y métodos. Esta anotación nos permite implementar inyección de dependencias basada en constructor, basada en campo o basada en método en nuestros componentes.
  • Para nuestro ejemplo, estoy utilizando la inyección de dependencia basada en método. Puedes descomentar el método del constructor para cambiar a la inyección de dependencia basada en constructor.

Ahora vamos a escribir una clase similar sin anotaciones.

package com.journaldev.spring.di.consumer;

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

public class MyXMLApplication {

	private MessageService service;

	//inyección de dependencia basada en constructor
//	public MyXMLApplication(MessageService svc) {
//		this.service = svc;
//	}
	
	//inyección de dependencia basada en setter
	public void setService(MessageService svc){
		this.service=svc;
	}

	public boolean processMessage(String msg, String rec) {
		// algún tipo de magia como validación, 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.

Configuración de Inyección de Dependencias de Primavera con Anotaciones

Para la configuración basada en anotaciones, necesitamos escribir una clase Configuradora que se utilizará para inyectar el bean de implementación real en la propiedad 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();
	}
}

Algunos puntos importantes relacionados con la clase anterior son:

  • @Configuration se utiliza para informar a Spring que es una clase de configuración.
  • La anotación @ComponentScan se usa con @Configuration para especificar los paquetes donde buscar clases de Componente.
  • La anotación @Bean se utiliza para informar al framework de Spring que este método debe usarse para obtener la implementación del bean para inyectar en las clases de Componente.

Vamos a escribir un programa simple para probar nuestro ejemplo de Inyección de Dependencias de Primavera basado en anotaciones.

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]");
		
		//cerrar el contexto
		context.close();
	}

}

AnnotationConfigApplicationContext es la implementación de la clase abstracta AbstractApplicationContext y se utiliza para realizar la conexión automática de servicios a componentes cuando se usan anotaciones. El constructor de AnnotationConfigApplicationContext toma una Clase como argumento que se utilizará para obtener la implementación del bean a inyectar en las clases de componentes. El método getBean(Clase) devuelve el objeto Componente y utiliza la configuración para realizar la conexión automática de los objetos. Los objetos de contexto consumen muchos recursos, así que deberíamos cerrarlos cuando hayamos terminado con ellos. Cuando ejecutamos el programa anterior, obtenemos la siguiente salida.

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

Configuración de Inyección de Dependencias de Spring basada en XML

Crearemos un archivo de configuración de Spring con los siguientes datos, el nombre del archivo puede ser cualquiera. Código del archivo 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>

Observa que el XML anterior contiene la configuración tanto para la inyección de dependencias de Spring basada en constructor como en setter. Dado que MyXMLApplication está utilizando el método setter para la inyección, la configuración del bean contiene el elemento property para la inyección. Para la inyección basada en constructor, tenemos que usar el elemento constructor-arg. El archivo de configuración XML se coloca en el directorio fuente, por lo que estará en el directorio de clases después de la compilación. Veamos cómo usar la configuración basada en XML con un programa simple.

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]");

		// cerrar el contexto
		context.close();
	}

}

ClassPathXmlApplicationContext se utiliza para obtener el objeto ApplicationContext proporcionando la ubicación de los archivos de configuración. Tiene varios constructores sobrecargados y también podemos proporcionar varios archivos de configuración. El resto del código es similar al programa de prueba de configuración basada en anotaciones; la única diferencia es la forma en que obtenemos el objeto ApplicationContext según nuestra elección de configuración. Cuando ejecutamos el programa anterior, obtenemos la siguiente salida.

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

Observa que parte de la salida está escrita por el Framework Spring. Dado que el Framework Spring utiliza log4j con fines de registro y no lo he configurado, la salida se está escribiendo en la consola.

Caso de prueba de inyección de dependencias de Spring

Uno de los principales beneficios de la inyección de dependencias en Spring es la facilidad de tener clases de servicio simuladas en lugar de utilizar servicios reales. Así que he combinado todo lo aprendido anteriormente y lo he escrito todo en una sola clase de prueba JUnit 4 para la inyección de dependencias en 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]"));
	}

}

La clase está anotada con la anotación @Configuration y @ComponentScan porque el método getMessageService() devuelve la implementación de burla de MessageService. Por eso getMessageService() está anotado con la anotación @Bean. Como estoy probando la clase MyApplication que está configurada con anotaciones, estoy utilizando AnnotationConfigApplicationContext y creando su objeto en el método setUp(). El contexto se cierra en el método tearDown(). El código del método test() simplemente obtiene el objeto del componente del contexto y lo prueba. ¿Te preguntas cómo hace el Framework Spring el auto-cableado y llama a los métodos que son desconocidos para el Framework Spring? Se hace con el uso extensivo de Java Reflection que podemos usar para analizar y modificar los comportamientos de las clases en tiempo de ejecución.

Descargar Proyecto de Inyección de Dependencias Spring

Descargue el proyecto de Inyección de Dependencias (DI) de Spring de la URL anterior y juegue con él para aprender más.

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