Сегодня мы рассмотрим внедрение зависимостей в Spring. Основные концепции в Spring Framework – это “Dependency Injection” и “Aspect Oriented Programming“. Ранее я писал об внедрении зависимостей в Java и о том, как мы можем использовать фреймворк Google Guice для автоматизации этого процесса в наших приложениях.
Внедрение зависимостей в Spring
Этот учебник направлен на предоставление подробной информации о примере внедрения зависимостей Spring с использованием как аннотаций, так и конфигурации на основе XML-файлов. Я также предоставлю пример тестового случая JUnit для приложения, поскольку простая тестировуемость является одним из основных преимуществ внедрения зависимостей. Я создал проект Maven spring-dependency-injection, структура которого выглядит как на изображении ниже.
Давайте рассмотрим каждый из компонентов по очереди.
Внедрение зависимостей Spring – Зависимости 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>
Текущая стабильная версия Spring Framework – 4.0.0.RELEASE, а текущая версия JUnit – 4.8.1. Если вы используете другие версии, то есть небольшой шанс того, что проект потребует некоторых изменений. Если вы построите проект, вы заметите, что в зависимости Maven также добавлены некоторые другие JAR-файлы из-за транзитивных зависимостей, так же как на изображении выше.
Внедрение зависимостей в Spring – Сервисные классы
Предположим, что мы хотим отправить электронное письмо и сообщение в Twitter пользователям. Для внедрения зависимостей нам нужно иметь базовый класс для сервисов. Так что у меня есть интерфейс MessageService
с одним методом для отправки сообщения.
package com.journaldev.spring.di.services;
public interface MessageService {
boolean sendMessage(String msg, String rec);
}
Теперь у нас будут фактические реализации классов для отправки электронных писем и сообщений в 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;
}
}
Теперь, когда наши сервисы готовы, мы можем перейти к классам компонентов, которые будут потреблять сервисы.
Внедрение зависимостей в 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 {
// Зависимость на основе полей
//@Autowired
private MessageService service;
// Зависимость на основе конструктора
// @Autowired
// public MyApplication(MessageService svc){
// this.service=svc;
// }
@Autowired
public void setService(MessageService svc){
this.service=svc;
}
public boolean processMessage(String msg, String rec){
//некоторая магия, например, валидация, логирование и т. д.
return this.service.sendMessage(msg, rec);
}
}
Несколько важных моментов о классе MyApplication:
- Аннотация
@Component
добавляется к классу, чтобы при сканировании Spring-фреймворком компонентов этот класс был рассмотрен как компонент. @Component аннотация может быть применена только к классу, и её политика удержания – Runtime. Если вы не знакомы с политикой удержания аннотаций, я бы посоветовал вам прочитать учебник по аннотациям в Java. @Autowired
аннотация используется, чтобы дать Spring знать, что требуется автосвязывание. Это можно применить к полям, конструктору и методам. Эта аннотация позволяет нам реализовать инъекцию зависимостей на основе конструктора, на основе поля или на основе метода в наших компонентах.- В нашем примере я использую инъекцию зависимостей на основе метода. Вы можете раскомментировать метод конструктора, чтобы переключиться на инъекцию зависимостей на основе конструктора.
Теперь давайте напишем аналогичный класс без аннотаций.
package com.journaldev.spring.di.consumer;
import com.journaldev.spring.di.services.MessageService;
public class MyXMLApplication {
private MessageService service;
//инъекция зависимостей на основе конструктора
// public MyXMLApplication(MessageService svc) {
// this.service = svc;
// }
//инъекция зависимостей на основе сеттера
public void setService(MessageService svc){
this.service=svc;
}
public boolean processMessage(String msg, String rec) {
// какая-то магия, такая как валидация, ведение журнала и т. д.
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 с использованием аннотаций
Для конфигурации на основе аннотаций нам нужно написать класс Configurator, который будет использоваться для внедрения реальной реализации бина в свойство компонента.
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 знал, что это класс конфигурации.@ComponentScan
аннотация используется с@Configuration
аннотацией для указания пакетов, в которых нужно искать классы компонентов.@Bean
аннотация используется, чтобы Spring-фреймворк знал, что этот метод должен использоваться для получения реализации бина для внедрения в классы компонентов.
Давайте напишем простую программу для тестирования нашего примера внедрения зависимостей Spring на основе аннотаций.
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]");
// закрываем контекст
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 [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
Мы создадим файл конфигурации 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 содержит конфигурацию как для внедрения зависимостей на основе конструктора, так и для внедрения зависимостей на основе методов установки Spring. Поскольку MyXMLApplication
использует метод установки для внедрения, конфигурация бина содержит элемент 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", "[email protected]");
// закрыть контекст
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 [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
Обратите внимание, что часть вывода написана Spring Framework. Поскольку Spring Framework использует log4j для регистрации, а я его не настроил, вывод направляется на консоль.
JUnit-тест кейс внедрения зависимостей Spring
Одним из основных преимуществ внедрения зависимостей в Spring является удобство использования макетных классов сервисов вместо фактических сервисов. Таким образом, я объединил всё изученное выше и написал всё в одном JUnit 4 тестовом классе для внедрения зависимостей в 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]"));
}
}
Класс аннотирован с использованием @Configuration
и аннотации @ComponentScan
, потому что метод getMessageService() возвращает имитацию реализации MessageService
. Вот почему getMessageService() аннотирована аннотацией @Bean
. Поскольку я тестирую класс MyApplication
, который настроен аннотацией, я использую AnnotationConfigApplicationContext
и создаю его объект в методе setUp(). Контекст закрывается в методе tearDown(). Код метода test() просто получает объект компонента из контекста и тестирует его. Интересно, как Spring Framework выполняет автосвязывание и вызов методов, которые неизвестны Spring Framework? Это делается с использованием интенсивного применения Java Reflection, которое мы можем использовать для анализа и модификации поведения классов во время выполнения.
Загрузите проект внедрения зависимостей Spring
Загрузите образец проекта внедрения зависимостей (DI) Spring по указанному выше URL и поиграйтесь с ним, чтобы узнать больше.
Source:
https://www.digitalocean.com/community/tutorials/spring-dependency-injection