Spring 依賴注入

今天我們將深入研究Spring依賴注入。Spring框架的核心概念是“依賴注入”和“面向切面編程”。我之前已經寫過關於Java依賴注入以及如何使用Google Guice框架自動化我們應用程序中的這個過程。

Spring依賴注入

這個教程旨在提供有關Spring依賴注入示例的詳細信息,包括基於注解的配置和基於XML文件的配置。我還將提供應用程序的JUnit測試用例示例,因為易於測試是依賴注入的主要好處之一。我已創建了一個名為spring-dependency-injection的maven項目,其結構如下圖所示。讓我們逐個查看每個組件。

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,如果您使用其他版本,則項目可能需要進行一些更改。如果您構建項目,您會注意到由於傳遞依賴關係,一些其他jar包也已添加到maven依賴項中,就像上面的圖片一樣。

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;
//	}
	
	//基於 setter 的依賴注入
	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類,該類將用於將實際實現的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這是一個配置類。
  • @ComponentScan注解與@Configuration注解一起使用,用於指定要查找組件類的包。
  • @Bean注解用於告訴Spring框架此方法應該用於獲取bean實現以注入組件類中。

讓我們編寫一個簡單的程序來測試我們基於注解的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();
	}

}

AnnotationConfigApplicationContextAbstractApplicationContext 抽象類別的實作,用於在使用註釋時將服務自動連線到組件。 AnnotationConfigApplicationContext 建構函式接受類別作為參數,該類別將用於獲取要注入到組件類別中的 Bean 實作。 getBean(Class) 方法返回組件物件並使用配置自動連線物件。上下文物件消耗資源,因此我們應該在完成使用後關閉它們。執行上述程式時,我們得到以下輸出。

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

基於 XML 的 Spring 依賴注入配置

我們將使用以下數據創建 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 使用設置器方法進行注入,因此 Bean 配置包含 property 元素進行注入。對於基於建構函式的注入,我們必須使用 constructor-arg 元素。配置 XML 文件放置在源目錄中,因此在構建後它將位於 classes 目錄中。讓我們看看如何使用基於 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 進行日誌記錄,而我並未配置它,所以輸出被寫入控制台。

Spring 依賴注入 JUnit 測試用例

在 Spring 中,依賴注入的一個主要優點是可以輕松地使用模擬服務類,而不是使用實際服務。因此,我將以上所有的學習結合起來,並在 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", "[email protected]"));
	}

}

這個類別被註釋為 @Configuration@ComponentScan,因為 getMessageService() 方法返回 MessageService 的模擬實現。這就是為什麼 getMessageService() 被註釋為 @Bean。由於我正在測試配置了注解的 MyApplication 類別,我正在使用 AnnotationConfigApplicationContext 並在 setUp() 方法中創建它的物件。上下文在 tearDown() 方法中關閉。 test() 方法的代碼只是從上下文中獲取組件物件並對其進行測試。你是否想知道 Spring 框架是如何進行自動連接和調用 Spring 框架中未知方法的呢?它是通過大量使用 Java 反射 來完成的,我們可以使用它來分析並在運行時修改類別的行為。

下載 Spring 依賴注入項目

從上面的 URL 下載示例 Spring 依賴注入(DI)項目並玩耍,以便更多地了解。

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