スプリングの依存性注入

今日はSpringの依存性インジェクションについて調べてみます。Springフレームワークの中心的な概念は、「依存性インジェクション」と「アスペクト指向プログラミング」です。以前にJavaの依存性インジェクションについて書いたこともありますし、Google Guiceフレームワークを使用してこのプロセスをアプリケーションで自動化する方法も述べました。

Springの依存性注入

このチュートリアルは、注釈ベースの設定とXMLファイルベースの設定の両方を使用したSpringの依存性注入の例についての詳細を提供することを目的としています。また、テストの容易さが依存性注入の主な利点の一つであるため、アプリケーションのJUnitテストケースの例も提供します。私は以下の画像のような構造を持つspring-dependency-injectionマベンプロジェクトを作成しました。それぞれのコンポーネントを一つずつ見ていきましょう。

スプリングの依存性注入 – メイブンの依存関係

pom.xml ファイルに Spring と JUnit の Maven 依存関係を追加しました。最終的な 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.scdev.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.scdev.spring.di.services;

public interface MessageService {

	boolean sendMessage(String msg, String rec);
}

今後、メールとツイッターメッセージを送信するための実際の実装クラスがあります。

package com.scdev.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.scdev.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;
	}

}

サービスの準備が整ったので、次はサービスを消費するコンポーネントクラスに移行できます。

春の依存性注入 – コンポーネントクラス

上記のサービスに対して、コンシューマークラスを作成しましょう。私たちは2つのコンシューマークラスを持つ予定です – 1つはSpringのアノテーションを使用した自動ワイヤリングがあり、もう1つはアノテーションなしで、ワイヤリングの設定はXMLの設定ファイルに記述されます。

package com.scdev.spring.di.consumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

import com.scdev.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 annotation is added to the class, so that when Spring framework will scan for the components, this class will be treated as component. @Component annotation can be applied only to the class and it’s retention policy is Runtime. If you are not not familiar with Annotations retention policy, I would suggest you to read java annotations tutorial.
  • @Autowired annotation is used to let Spring know that autowiring is required. This can be applied to field, constructor and methods. This annotation allows us to implement constructor-based, field-based or method-based dependency injection in our components.
  • For our example, I am using method-based dependency injection. You can uncomment the constructor method to switch to constructor based dependency injection.

今度はアノテーションを使わずに似たようなクラスを書いてみましょう。 (Kondo wa anotēshon o tsukawazu ni nita yōna kurasu o kaite mimashou.)

package com.scdev.spring.di.consumer;

import com.scdev.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の依存性注入設定

アノテーションベースの設定には、実装されたBeanをコンポーネントのプロパティにインジェクトするために使用されるConfiguratorクラスを作成する必要があります。

package com.scdev.spring.di.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.scdev.spring.di.services.EmailService;
import com.scdev.spring.di.services.MessageService;

@Configuration
@ComponentScan(value={"com.scdev.spring.di.consumer"})
public class DIConfiguration {

	@Bean
	public MessageService getMessageService(){
		return new EmailService();
	}
}

上記のクラスに関連するいくつかの重要なポイントは以下の通りです。

  • @Configuration annotation is used to let Spring know that it’s a Configuration class.
  • @ComponentScan annotation is used with @Configuration annotation to specify the packages to look for Component classes.
  • @Bean annotation is used to let Spring framework know that this method should be used to get the bean implementation to inject in Component classes.

私たちのアノテーションベースのSpringのDIの例をテストするために、シンプルなプログラムを書きましょう。

package com.scdev.spring.di.test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.scdev.spring.di.configuration.DIConfiguration;
import com.scdev.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", "scdev@abc.com");
		
		//close the context
		context.close();
	}

}

AnnotationConfigApplicationContextは、AbstractApplicationContext抽象クラスの実装であり、アノテーションが使用される場合にコンポーネントにサービスをオートワイヤリングするために使用されます。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 scdev@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の依存性注入を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.scdev.spring.di.consumer.MyXMLApplication">
		<constructor-arg>
			<bean class="com.scdev.spring.di.services.TwitterService" />
		</constructor-arg>
	</bean>
-->
	<bean id="twitter" class="com.scdev.spring.di.services.TwitterService"></bean>
	<bean id="MyXMLApp" class="com.scdev.spring.di.consumer.MyXMLApplication">
		<property name="service" ref="twitter"></property>
	</bean>
</beans>

上記のXMLには、コンストラクタベースとセッターベースのSpring依存性注入の両方の設定が含まれていることに注目してください。 MyXMLApplicationはインジェクションにセッターメソッドを使用しているため、beanの設定にはインジェクションのためのproperty要素が含まれています。コンストラクタベースのインジェクションには、constructor-arg要素を使用する必要があります。設定XMLファイルはソースディレクトリに配置されているため、ビルド後にはクラスディレクトリにあります。簡単なプログラムでXMLベースの設定をどのように使用するかを見てみましょう。

package com.scdev.spring.di.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.scdev.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", "scdev@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 scdev@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を使用しており、それが設定されていないため、出力がコンソールに書き込まれています。

スプリング依存性注入のJUnitテストケース

スプリングにおける依存性注入の主な利点の1つは、実際のサービスを使用する代わりにモックサービスクラスを簡単に取得できることです。そのため、上記の全ての学習を組み合わせて、スプリングの依存性注入に関するすべてを1つのJUnit 4テストクラスにまとめました。

package com.scdev.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.scdev.spring.di.consumer.MyApplication;
import com.scdev.spring.di.services.MessageService;

@Configuration
@ComponentScan(value="com.scdev.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", "scdev@abc.com"));
	}

}

メッセージサービスのモック実装を返すgetMessageService()メソッドがあるため、クラスは@Configurationと@ComponentScanアノテーションで注釈が付けられています。そのため、getMessageService()メソッドには@Beanアノテーションが付けられています。アノテーションで構成されたMyApplicationクラスをテストしているため、setUp()メソッドでAnnotationConfigApplicationContextのオブジェクトを作成しています。contextはtearDown()メソッドでクローズされます。test()メソッドのコードは、単にコンテキストからコンポーネントオブジェクトを取得してテストしています。Spring FrameworkがどのようにオートワイヤリングおよびSpring Frameworkに知られていないメソッドの呼び出しを行っているのか気になりますか。それは、Javaのリフレクションを非常に多く使用することによって行われています。これにより、クラスの動作を実行時に分析および変更することができます。

スプリングの依存性注入プロジェクトをダウンロードしてください。

上記のURLからサンプルのSpring Dependency Injection(DI)プロジェクトをダウンロードし、それを使ってさらに学ぶために試してみてください。

コメントを残す 0

Your email address will not be published. Required fields are marked *