Поиск по сайту:

Внедрение зависимостей Spring


Сегодня мы рассмотрим Spring Dependency Injection. Основными понятиями Spring Framework являются «Внедрение зависимостей» и «Аспектно-ориентированное программирование». Ранее я писал о фреймворке Google Guice для автоматизации этого процесса в наших приложениях.

Внедрение зависимостей Spring

Внедрение зависимостей Spring — Зависимости Maven

Я добавил зависимости Spring и JUnit maven в файл pom.xml, окончательный код pom.xml приведен ниже.

<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>

Текущая стабильная версия Spring Framework – 4.0.0.RELEASE, а текущая версия JUnit – 4.8.1. Если вы используете какие-либо другие версии, существует небольшая вероятность того, что проект потребует некоторых изменений. Если вы создадите проект, вы заметите, что некоторые другие jar-файлы также добавляются к зависимостям maven из-за транзитивных зависимостей, как показано на изображении выше.

Внедрение зависимостей Spring — классы обслуживания

Допустим, мы хотим отправить пользователям сообщение электронной почты и сообщение в Твиттере. Для внедрения зависимостей нам нужен базовый класс для сервисов. Итак, у меня есть интерфейс MessageService с объявлением одного метода для отправки сообщения.

package com.journaldev.spring.di.services;

public interface MessageService {

	boolean sendMessage(String msg, String rec);
}

Теперь у нас будут настоящие классы реализации для отправки сообщений электронной почты и твиттера.

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;
	}

}

Теперь, когда наши службы готовы, мы можем перейти к классам компонентов, которые будут потреблять службу.

Внедрение зависимостей Spring — классы компонентов

Давайте напишем потребительский класс для вышеперечисленных сервисов. У нас будет два потребительских класса: один с аннотациями Spring для автоматического подключения, а другой без аннотаций, и конфигурация подключения будет предоставлена в файле конфигурации 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 {

	//field-based dependency injection
	//@Autowired
	private MessageService service;
	
//	constructor-based 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){
		//some magic like validation, logging etc
		return this.service.sendMessage(msg, rec);
	}
}

Несколько важных моментов о классе MyApplication:

    К классу добавляется аннотация
  • @Component, поэтому, когда среда Spring будет сканировать компоненты, этот класс будет рассматриваться как компонент. руководство по аннотациям Java.
  • Аннотация
  • @Autowired используется, чтобы сообщить Spring, что требуется автоматическое подключение. Это может быть применено к полю, конструктору и методам. Эта аннотация позволяет нам реализовать внедрение зависимостей на основе конструктора, поля или метода в наши компоненты.
  • В нашем примере я использую внедрение зависимостей на основе методов. Вы можете раскомментировать метод конструктора, чтобы переключиться на внедрение зависимостей на основе конструктора.

Теперь давайте напишем аналогичный класс без аннотаций.

package com.journaldev.spring.di.consumer;

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

public class MyXMLApplication {

	private MessageService service;

	//constructor-based dependency injection
//	public MyXMLApplication(MessageService svc) {
//		this.service = svc;
//	}
	
	//setter-based dependency injection
	public void setService(MessageService svc){
		this.service=svc;
	}

	public boolean processMessage(String msg, String rec) {
		// some magic like validation, logging etc
		return this.service.sendMessage(msg, rec);
	}
}

Простой класс приложения, использующий службу. Для конфигурации на основе XML мы можем использовать внедрение зависимостей Spring на основе конструктора или внедрение зависимостей Spring на основе методов. Обратите внимание, что подходы к внедрению на основе методов и сеттеров одинаковы, просто некоторые предпочитают называть его основанным на сеттерах, а некоторые называют его основанным на методах.

Конфигурация инъекции зависимостей Spring с аннотациями

Для конфигурации на основе аннотаций нам нужно написать класс Configurator, который будет использоваться для внедрения фактического bean-компонента реализации в свойство компонента.

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();
	}
}

Некоторые важные моменты, связанные с вышеуказанным классом:

    Аннотация
  • @Configuration используется, чтобы Spring знал, что это класс Configuration.
  • Аннотация
  • @ComponentScan используется с аннотацией @Configuration, чтобы указать пакеты для поиска классов компонентов.
  • Аннотация
  • @Bean используется, чтобы сообщить платформе Spring, что этот метод следует использовать для получения реализации bean-компонента для внедрения в классы компонентов.

Давайте напишем простую программу для тестирования нашего примера Spring Dependency Injection на основе аннотаций.

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", "pankaj@abc.com");
		
		//close the context
		context.close();
	}

}

AnnotationConfigApplicationContext — это реализация абстрактного класса AbstractApplicationContext, которая используется для автоматического связывания служб с компонентами при использовании аннотаций. Конструктор AnnotationConfigApplicationContext принимает класс в качестве аргумента, который будет использоваться для внедрения реализации компонента в классы компонентов. Метод getBean(Class) возвращает объект Component и использует конфигурацию для автоматического связывания объектов. Объекты контекста требуют больших ресурсов, поэтому мы должны закрыть их, когда закончим с этим. Когда мы запускаем вышеуказанную программу, мы получаем вывод ниже.

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 pankaj@abc.com 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

Мы создадим файл конфигурации Spring с данными ниже, имя файла может быть любым. код 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>

Обратите внимание, что приведенный выше XML содержит конфигурацию для внедрения пружинной зависимости как на основе конструктора, так и на основе установщика. Поскольку MyXMLApplication использует метод setter для внедрения, конфигурация bean-компонента содержит элемент property для внедрения. Для внедрения на основе конструктора мы должны использовать элемент constructor-arg. XML-файл конфигурации помещается в исходный каталог, поэтому после сборки он будет находиться в каталоге классов. Давайте посмотрим, как использовать конфигурацию на основе XML с простой программой.

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", "pankaj@abc.com");

		// close the context
		context.close();
	}

}

ClassPathXmlApplicationContext используется для получения объекта ApplicationContext путем указания местоположения файлов конфигурации. Он имеет несколько перегруженных конструкторов, и мы также можем предоставить несколько файлов конфигурации. Остальная часть кода похожа на программу тестирования конфигурации на основе аннотаций, единственное отличие состоит в том, как мы получаем объект ApplicationContext на основе нашего выбора конфигурации. Когда мы запускаем вышеуказанную программу, мы получаем следующий вывод.

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 pankaj@abc.com 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

Обратите внимание, что часть вывода написана Spring Framework. Поскольку Spring Framework использует log4j для ведения журнала, и я не настроил его, вывод записывается на консоль.

Тестовый пример Spring Dependency Injection JUnit

Одним из основных преимуществ внедрения зависимостей в spring является простота использования фиктивных классов обслуживания вместо использования реальных служб. Поэтому я объединил все знания, полученные выше, и написал все в одном тестовом классе JUnit 4 для внедрения зависимостей весной.

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", "pankaj@abc.com"));
	}

}

Класс снабжен аннотациями @Configuration и @ComponentScan, поскольку метод getMessageService() возвращает фиктивную реализацию MessageService. Вот почему getMessageService() снабжен аннотацией @Bean. Поскольку я тестирую класс MyApplication, настроенный с аннотацией, я использую AnnotationConfigApplicationContext и создаю его объект в методе setUp(). Контекст закрывается в методе tearDown(). Код метода test() просто получает объект компонента из контекста и проверяет его. Вам интересно, как Spring Framework выполняет автоматическое связывание и вызывает методы, неизвестные Spring Framework. Это достигается за счет интенсивного использования Java Reflection, который мы можем использовать для анализа и изменения поведения классов во время выполнения.

Скачать проект Spring Dependency Injection

Загрузите пример проекта Spring Dependency Injection (DI) по указанному выше URL-адресу и поэкспериментируйте с ним, чтобы узнать больше.