Injeção de Dependência no Spring

Hoje vamos analisar a Injeção de Dependência do Spring. Os conceitos principais do Framework Spring são “Injeção de Dependência” e “Programação Orientada a Aspectos”. Eu já escrevi anteriormente sobre Injeção de Dependência em Java e como podemos utilizar o framework Google Guice para automatizar esse processo em nossas aplicações.

Injeção de Dependência do Spring

Este tutorial tem como objetivo fornecer detalhes sobre o exemplo de Injeção de Dependência do Spring, tanto com configuração baseada em anotações quanto com configuração baseada em arquivo XML. Também fornecerei um exemplo de caso de teste JUnit para a aplicação, já que a facilidade de testabilidade é um dos principais benefícios da injeção de dependência. Eu criei o projeto Maven spring-dependency-injection, cuja estrutura se parece com a imagem abaixo. Vamos analisar cada um dos componentes um por um.

Injeção de Dependência do Spring – Dependências do 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>

A versão estável atual do Spring Framework é 4.0.0.RELEASE e a versão atual do JUnit é 4.8.1, se você estiver usando outras versões, pode haver uma pequena chance de que o projeto precise de algumas alterações. Se você construir o projeto, notará que outros JARs também são adicionados às dependências do Maven devido a dependências transitivas, assim como na imagem acima.

Injeção de Dependência Spring – Classes de Serviço

Digamos que desejamos enviar mensagens de e-mail e mensagens de Twitter para os usuários. Para injeção de dependência, precisamos ter uma classe base para os serviços. Então, tenho a interface MessageService com declaração de método único para enviar mensagem.

package com.journaldev.spring.di.services;

public interface MessageService {

	boolean sendMessage(String msg, String rec);
}

Agora teremos classes de implementação reais para enviar e-mail e mensagem no 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;
	}

}

Agora que nossos serviços estão prontos, podemos passar para as classes de Componente que irão consumir o serviço.

Injeção de Dependência Spring – Classes de Componente

Vamos escrever uma classe consumidora para os serviços acima. Teremos duas classes consumidoras – uma com anotações Spring para autowiring e outra sem anotação e a configuração de conexão será fornecida no arquivo de configuração 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 {

	//injeção de dependência baseada em campo
	//@Autowired
	private MessageService service;
	
//	injeção de dependência baseada em construtor	
//	@Autowired
//	public MyApplication(MessageService svc){
//		this.service=svc;
//	}
	
	@Autowired
	public void setService(MessageService svc){
		this.service=svc;
	}
	
	public boolean processMessage(String msg, String rec){
		//alguma mágica como validação, logging etc
		return this.service.sendMessage(msg, rec);
	}
}

Alguns pontos importantes sobre a classe MyApplication:

  • A anotação @Component é adicionada à classe, para que quando o framework Spring escanear os componentes, esta classe seja tratada como um componente. A anotação @Component só pode ser aplicada à classe e sua política de retenção é em Tempo de Execução. Se você não está familiarizado com a política de retenção de anotações, sugiro que leia o tutorial de anotações em Java. A anotação
  • @Autowired é usada para informar ao Spring que a injeção automática é necessária. Isso pode ser aplicado a campos, construtores e métodos. Esta anotação nos permite implementar injeção de dependência baseada em construtor, baseada em campo ou baseada em método em nossos componentes.
  • Para o nosso exemplo, estou usando injeção de dependência baseada em método. Você pode descomentar o método construtor para alternar para injeção de dependência baseada em construtor.

Agora vamos escrever uma classe similar sem anotações.

package com.journaldev.spring.di.consumer;

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

public class MyXMLApplication {

	private MessageService service;

	 // injeção de dependência baseada em construtor 
 // public MyXMLApplication(MessageService svc) { 
 // this.service = svc; 
//	}
	
	 // injeção de dependência baseada em setter 
	public void setService(MessageService svc){
		this.service=svc;
	}

	public boolean processMessage(String msg, String rec) {
		 // algum tipo de mágica como validação, registro, 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.

Configuração de Injeção de Dependência do Spring com Anotações

Para configuração baseada em anotações, precisamos escrever uma classe Configuradora que será usada para injetar o bean de implementação real na propriedade do 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();
	}
}

Alguns pontos importantes relacionados à classe acima são:

  • @Configuration é usada para informar ao Spring que é uma classe de configuração.
  • @ComponentScan é usada com @Configuration para especificar os pacotes a serem pesquisados para classes de componentes.
  • @Bean é usada para informar ao framework do Spring que este método deve ser usado para obter a implementação do bean para injetar em classes de componentes.

Vamos escrever um programa simples para testar nosso exemplo de Injeção de Dependência do Spring baseado em anotações.

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

}

AnnotationConfigApplicationContext é a implementação da classe abstrata AbstractApplicationContext e é usada para realizar a ligação automática dos serviços aos componentes quando são usadas anotações. O construtor de AnnotationConfigApplicationContext recebe uma classe como argumento que será usada para obter a implementação do bean para injetar nas classes de componente. O método getBean(Classe) retorna o objeto Componente e utiliza a configuração para realizar a ligação automática dos objetos. Os objetos de contexto consomem muitos recursos, então devemos fechá-los quando terminarmos de usá-los. Quando executamos o programa acima, obtemos a saída abaixo.

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

Configuração Baseada em XML para Injeção de Dependência do Spring

Vamos criar um arquivo de configuração do Spring com os dados abaixo, o nome do arquivo pode ser qualquer coisa. Código do arquivo 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>

Observe que o XML acima contém configuração tanto para injeção de dependência baseada em construtor quanto para injeção de dependência baseada em setter do Spring. Como MyXMLApplication está usando o método setter para injeção, a configuração do bean contém o elemento property para injeção. Para injeção baseada em construtor, temos que usar o elemento constructor-arg. O arquivo de configuração XML é colocado no diretório de origem, então estará no diretório de classes após a compilação. Vamos ver como usar a configuração baseada em XML com um programa simples.

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

		// fechar o contexto
		context.close();
	}

}

ClassPathXmlApplicationContext é usado para obter o objeto ApplicationContext fornecendo a localização dos arquivos de configuração. Ele possui vários construtores sobrecarregados e podemos fornecer vários arquivos de configuração. O restante do código é semelhante a um programa de teste de configuração baseada em anotações, a única diferença é a maneira como obtemos o objeto ApplicationContext com base na nossa escolha de configuração. Ao executar o programa acima, obtemos a seguinte saída.

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

Observe que parte da saída é escrita pelo Spring Framework. Como o Spring Framework usa o log4j para fins de registro e não o configurei, a saída está sendo gravada no console.

Caso de Teste JUnit para Injeção de Dependência no Spring

Um dos principais benefícios da injeção de dependência no Spring é a facilidade de ter classes de serviço simuladas em vez de usar serviços reais. Portanto, combinei todas as aprendizagens acima e escrevi tudo em uma única classe de teste JUnit 4 para injeção de dependência no 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]"));
	}

}

A classe é anotada com @Configuration e @ComponentScan porque o método getMessageService() retorna a implementação mock de MessageService. Por isso, getMessageService() é anotado com @Bean. Como estou testando a classe MyApplication configurada com anotações, estou usando AnnotationConfigApplicationContext e criando seu objeto no método setUp(). O contexto é fechado no método tearDown(). O código do método test() está apenas obtendo o objeto componente do contexto e testando-o. Você se pergunta como o Spring Framework faz a injeção automática e chama os métodos que são desconhecidos para o Spring Framework. Isso é feito com o uso intensivo do Java Reflection, que podemos usar para analisar e modificar os comportamentos das classes em tempo de execução.

Baixe o Projeto de Injeção de Dependência Spring

Baixe o projeto de injeção de dependência (DI) Spring de amostra do URL acima e brinque com ele para aprender mais.

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