
JavaやKotlinでWebアプリケーションを作るのであればSpringFrameworkを利用するのが一般的であり、SpringFrameworkと言えばDIコンテナの代表格です。今回は、SpringFrameworkの初学者向けにIOCとDI方法をまとめて紹介します。
はじめに
DI(Dependency Injection)とは、日本語では「依存性注入」と訳されているため初学者には意味不明ですが、要はクラス同士を疎結合で利用する手法です。合わせて、ありのままのクラスを表したPOJO(Plain Old Java Object)という言葉もあります。大昔のEJB全盛期は密結合で実装せざるを得なかったらしく、Plain Oldというあたりは歴史を物語っているようですね。現在ではDIを使うことで各クラス間は疎結合に保たれるので、POJOをそのまま組み合わせて利用するのが普通です。SpringFrameworkのIoC(Inversion of Control)、日本語では「制御の逆転」ですが、要はDIの利用方法と捉えれば良いと思います。DIの利用方法は、登場した2003年当時はXMLによる方法しか提供されていませんでしたが、現在ではアノテーションが主流となっています。とはいっても、おそらく現場レベルで最も多く使われているのはXMLとアノテーションのハイブリッドでしょう。アノテーションは便利ですが、XMLには柔軟性があります。
今回は、SpringFrameworkのDIの利用方法(「IoC方法」と表記)およびDIの使い方をまとめて紹介します。
前提
以下がインストールされている前提で進めます。
- Java10
- Maven3
細かいバージョンは「環境」を参照して下さい。
サンプルアプリケーションを作る
まずはMavenで雛形を作成し、DIするためだけに拡張したHelloWorldのサンプルアプリケーションを作ります。
$ mvn -B archetype:generate \
> -DarchetypeGroupId=org.apache.maven.archetypes \
> -DgroupId=com.example.project \
> -DartifactId=spring-di-sample-app
$ cd spring-di-sample-app/
$ tree
.
├── pom.xml
└── src
├── main
│ └── java
│ └── com
│ └── example
│ └── project
│ └── App.java
└── test
└── java
└── com
└── example
└── project
└── AppTest.java
$ rm src/main/java/com/example/project/App.java
$ rm src/test/java/com/example/project/AppTest.java
...(IDEで編集)...
$ tree
.
├── pom.xml
├── spring-di-sample-app.iml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── project
│ │ ├── SampleApp.java
│ │ ├── domain
│ │ │ ├── HelloWorld.java
│ │ │ └── HelloWorldImpl.java
│ │ └── service
│ │ ├── EnglishGreetingServiceImpl.java
│ │ ├── JapaneseGreetingServiceImpl.java
│ │ └── WorldGreetingService.java
│ └── resources
└── test
└── java
└── com
└── example
└── project
それぞれのソースコードは以下です。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.project</groupId>
<artifactId>spring-di-sample-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>spring-di-sample-app</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>10</java.version>
<spring.version>5.0.7.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<target>${java.version}</target>
<source>${java.version}</source>
<release>${java.version}</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
HelloWorld.java
package com.example.project.domain;
public interface HelloWorld {
public String expression();
}
HelloWorldImpl.java
package com.example.project.domain;
import com.example.project.service.WorldGreetingService;
import java.time.LocalDateTime;
public class HelloWorldImpl implements HelloWorld {
private WorldGreetingService greetingService;
public HelloWorldImpl(WorldGreetingService greetingService) {
this.greetingService = greetingService;
}
@Override
public String expression() {
LocalDateTime dateTime = LocalDateTime.now();
int hour = dateTime.getHour();
String expression;
if (hour >= 4 && hour <= 9) {
expression = greetingService.morningGreeting();
} else if (hour >= 10 && hour <= 17) {
expression = greetingService.afternoonGreeting();
} else {
expression = greetingService.eveningGreeting();
}
return expression;
}
}
WorldGreetingService.java
package com.example.project.service;
public interface WorldGreetingService {
public String morningGreeting();
public String afternoonGreeting();
public String eveningGreeting();
}
EnglishGreetingServiceImpl.java
package com.example.project.service;
public class EnglishGreetingServiceImpl implements WorldGreetingService {
private String targetName;
public EnglishGreetingServiceImpl(String targetName) {
this.targetName = targetName;
}
@Override
public String morningGreeting() {
return "Good morning, " + targetName + ".";
}
@Override
public String afternoonGreeting() {
return "Hello, " + targetName + ".";
}
@Override
public String eveningGreeting() {
return "Good evening, " + targetName + ".";
}
}
JapaneseGreetingServiceImpl.java
package com.example.project.service;
public class JapaneseGreetingServiceImpl implements WorldGreetingService {
private String targetName;
public JapaneseGreetingServiceImpl(String targetName) {
this.targetName = targetName;
}
@Override
public String morningGreeting() {
return "おはよう、 " + targetName + "。";
}
@Override
public String afternoonGreeting() {
return "こんにちは、 " + targetName + "。";
}
@Override
public String eveningGreeting() {
return "こんばんは、 " + targetName + "。";
}
}
XMLのIoC方法
XMLのIoCは、Spring用のbeans.xmlを作成し、その設定をApplicationContextに読み込んで実現します。
ファイルパスによるXMLファイルの設定
プロジェクトフォルダ直下に「beans.xml」ファイルを作成します。
「beans.xml」は以下のようにします。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="helloworld" class="com.example.project.domain.HelloWorldImpl">
<constructor-arg ref="englishGreeting"></constructor-arg>
</bean>
<bean name="englishGreeting"
class="com.example.project.service.EnglishGreetingServiceImpl">
<constructor-arg name="targetName" value="World"></constructor-arg>
</bean>
<bean name="japaneseGreeting"
class="com.example.project.service.JapaneseGreetingServiceImpl">
<constructor-arg name="targetName" value="世界"></constructor-arg>
</bean>
</beans>
メインクラス「SampleApp.java」は以下のようにします。「FileSystemXmlApplicationContext」を使用することで、プロジェクトフォルダ配下のパスにあるSpringのXMLファイルを使用できます。
package com.example.project;
import com.example.project.domain.HelloWorld;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class SampleApp {
public static void main(String[] args) {
final ConfigurableApplicationContext context = new FileSystemXmlApplicationContext("beans.xml");
final HelloWorld helloWorld = (HelloWorld) context.getBean("helloworld");
System.out.println(helloWorld.expression());
context.close();
}
}
実行してみましょう。
$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.SampleApp"
...
Good evening, World.
...
正しく動きました。
クラスパスによるXMLファイルの設定
resourcesフォルダに「beans.xml」ファイルを移動します。
メインクラス「SampleApp.java」は以下のよう修正します。「ConfigurableApplicationContext」を使用することで、クラスパスのあるresources配下にあるSpringのXMLファイルを使用できます。
package com.example.project;
import com.example.project.domain.HelloWorld;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SampleApp {
public static void main(String[] args) {
final ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
final HelloWorld helloWorld = (HelloWorld) context.getBean("helloworld");
System.out.println(helloWorld.expression());
context.close();
}
}
実行してみましょう。
$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.SampleApp"
...
Good evening, World.
...
先程と同じく正しく動きました。
XMLのDIの使い方
コンストラクタ・インジェクション
コンストラクタを利用してDIする方法で、すでにサンプルアプリケーションはコンストラクタ・インジェクションで実装しています。DIするフィールドをコンストラクタで定義し、beans.xmlでconstructor-argにより設定します。
HelloWorldImpl.java(抜粋)
package com.example.project.domain;
...
public class HelloWorldImpl implements HelloWorld {
private WorldGreetingService greetingService;
public HelloWorldImpl(WorldGreetingService greetingService) {
this.greetingService = greetingService;
}
...
EnglishGreetingServiceImpl.java(抜粋)
package com.example.project.service;
public class EnglishGreetingServiceImpl implements WorldGreetingService {
private String targetName;
public EnglishGreetingServiceImpl(String targetName) {
this.targetName = targetName;
}
...
}
JapaneseGreetingServiceImpl.java(抜粋)
package com.example.project.service;
public class JapaneseGreetingServiceImpl implements WorldGreetingService {
private String targetName;
public JapaneseGreetingServiceImpl(String targetName) {
this.targetName = targetName;
}
...
}
beans.xml(再掲)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="helloworld" class="com.example.project.domain.HelloWorldImpl">
<constructor-arg ref="englishGreeting"></constructor-arg>
</bean>
<bean name="englishGreeting"
class="com.example.project.service.EnglishGreetingServiceImpl">
<constructor-arg name="targetName" value="World"></constructor-arg>
</bean>
<bean name="japaneseGreeting"
class="com.example.project.service.JapaneseGreetingServiceImpl">
<constructor-arg name="targetName" value="世界"></constructor-arg>
</bean>
</beans>
セッター・インジェクション
DIするフィールドをセッターで定義し、beans.xmlでpropertyにより設定します。先程のコンストラクタをセッターに書き直してみましょう。
HelloWorldImpl.java(修正後)
package com.example.project.domain;
import com.example.project.service.WorldGreetingService;
import java.time.LocalDateTime;
public class HelloWorldImpl implements HelloWorld {
private WorldGreetingService greetingService;
public void setGreetingService(WorldGreetingService greetingService) {
this.greetingService = greetingService;
}
@Override
public String expression() {
LocalDateTime dateTime = LocalDateTime.now();
int hour = dateTime.getHour();
String expression;
if (hour >= 4 && hour <= 9) {
expression = greetingService.morningGreeting();
} else if (hour >= 10 && hour <= 17) {
expression = greetingService.afternoonGreeting();
} else {
expression = greetingService.eveningGreeting();
}
return expression;
}
}
EnglishGreetingServiceImpl.java(修正後)
package com.example.project.service;
public class EnglishGreetingServiceImpl implements WorldGreetingService {
private String targetName;
public void setTargetName(String targetName) {
this.targetName = targetName;
}
@Override
public String morningGreeting() {
return "Good morning, " + targetName + ".";
}
@Override
public String afternoonGreeting() {
return "Hello, " + targetName + ".";
}
@Override
public String eveningGreeting() {
return "Good evening, " + targetName + ".";
}
}
JapaneseGreetingServiceImpl.java(修正後)
package com.example.project.service;
public class JapaneseGreetingServiceImpl implements WorldGreetingService {
private String targetName;
public void setTargetName(String targetName) {
this.targetName = targetName;
}
@Override
public String morningGreeting() {
return "おはよう、 " + targetName + "。";
}
@Override
public String afternoonGreeting() {
return "こんにちは、 " + targetName + "。";
}
@Override
public String eveningGreeting() {
return "こんばんは、 " + targetName + "。";
}
}
beans.xml(修正後)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="helloworld" class="com.example.project.domain.HelloWorldImpl">
<property name="greetingService" ref="japaneseGreeting"></property>
</bean>
<bean name="englishGreeting"
class="com.example.project.service.EnglishGreetingServiceImpl">
<property name="targetName" value="World"></property>
</bean>
<bean name="japaneseGreeting"
class="com.example.project.service.JapaneseGreetingServiceImpl">
<property name="targetName" value="世界"></property>
</bean>
</beans>
実行してみましょう。
$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.SampleApp"
...
こんにちは、 世界。
...
正しく動きました。
どちらを使うべきか?
基本的にコンストラクタ・インジェクションを使うのが良いです。理由は、コンストラクタでDIした場合、DIした値はイミュータブルになり、安全に扱えるためです。ただし、コンストラクタの引数が多い場合や必須ではないオプション値は、セッター・インジェクションや、init-methodまたはdefault-init-methodによる初期化、@PostConstructによる初期化などを検討するとよいでしょう。
アノテーションのIoC方法
アノテーションのIoCは、beans.xmlの設定変更またはアノテーションのみによって有効化できます。
XMLファイルによる設定(Autowiredのみ)
徐々にXMLからアノテーションへ修正していきましょう。まずは、「annotation-config」を有効化して、beans.xmlのrefで結合していたクラスをAutowiredで結合するように修正します。
beans.xml(修正後)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean name="helloworld" class="com.example.project.domain.HelloWorldImpl">
</bean>
<bean name="englishGreeting"
class="com.example.project.service.EnglishGreetingServiceImpl">
</bean>
<bean name="japaneseGreeting"
class="com.example.project.service.JapaneseGreetingServiceImpl">
</bean>
</beans>
HelloWorldImpl.java(修正後)
セッターを削除し、@Autowiredを追加します。refの代わりに@Qualifierで対象を指定しています。
package com.example.project.domain;
import com.example.project.service.WorldGreetingService;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class HelloWorldImpl implements HelloWorld {
@Autowired
@Qualifier("englishGreeting")
private WorldGreetingService greetingService;
@Override
public String expression() {
LocalDateTime dateTime = LocalDateTime.now();
int hour = dateTime.getHour();
String expression;
if (hour >= 4 && hour <= 9) {
expression = greetingService.morningGreeting();
} else if (hour >= 10 && hour <= 17) {
expression = greetingService.afternoonGreeting();
} else {
expression = greetingService.eveningGreeting();
}
return expression;
}
}
EnglishGreetingServiceImpl.java(修正後)
beans.xmlで設定していた値はひとまず@Valueで設定します。
package com.example.project.service;
import org.springframework.beans.factory.annotation.Value;
public class EnglishGreetingServiceImpl implements WorldGreetingService {
@Value("Annotation")
private String targetName;
@Override
public String morningGreeting() {
return "Good morning, " + targetName + ".";
}
@Override
public String afternoonGreeting() {
return "Hello, " + targetName + ".";
}
@Override
public String eveningGreeting() {
return "Good evening, " + targetName + ".";
}
}
JapaneseGreetingServiceImpl.java(修正後)
package com.example.project.service;
import org.springframework.beans.factory.annotation.Value;
public class JapaneseGreetingServiceImpl implements WorldGreetingService {
@Value("アノテーション")
private String targetName;
@Override
public String morningGreeting() {
return "おはよう、 " + targetName + "。";
}
@Override
public String afternoonGreeting() {
return "こんにちは、 " + targetName + "。";
}
@Override
public String eveningGreeting() {
return "こんばんは、 " + targetName + "。";
}
}
実行してみましょう。
$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.SampleApp"
...
Hello, Annotation.
...
ちゃんと動きますね。
XMLファイルによる設定(コンポーネントスキャン)
続いて、beans.xmlに「component-scan」を設定して、コンポーネントスキャンを有効化します。
beans.xml(修正後)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.project"/>
</beans>
Beanはコンポーネントスキャンによって生成されるので、もはや定義すら不要になりました。
HelloWorldImpl.java(抜粋)
@Componentを追加しただけです。
package com.example.project.domain;
import com.example.project.service.WorldGreetingService;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("helloworld")
public class HelloWorldImpl implements HelloWorld {
@Autowired
@Qualifier("japaneseGreeting")
private WorldGreetingService greetingService;
...
}
EnglishGreetingServiceImpl.java(抜粋)
@Componentを追加しただけです。
package com.example.project.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("englishGreeting")
public class EnglishGreetingServiceImpl implements WorldGreetingService {
...
}
JapaneseGreetingServiceImpl.java(抜粋)
@Componentを追加しただけです。
package com.example.project.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("japaneseGreeting")
public class JapaneseGreetingServiceImpl implements WorldGreetingService {
...
}
実行してみましょう。
$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.SampleApp"
...
こんにちは、 アノテーション。
...
OKです。
アノテーションによる設定
いよいよ設定ファイルを全てアノテーションに修正します。
まずは、beans.xmlを削除しましょう。
$ rm src/main/resources/beans.xml
そして、IDEで必要なファイルを追加すると以下のようになります。
それぞれのソースコードは以下のようになります。
EnglishGreetingServiceImpl.java(修正後)
@Componentを削除します。
package com.example.project.service;
public class EnglishGreetingServiceImpl implements WorldGreetingService {
private String targetName;
public EnglishGreetingServiceImpl(String targetName) {
this.targetName = targetName;
}
@Override
public String morningGreeting() {
return "Good morning, " + targetName + ".";
}
@Override
public String afternoonGreeting() {
return "Hello, " + targetName + ".";
}
@Override
public String eveningGreeting() {
return "Good evening, " + targetName + ".";
}
}
English.java(新規作成)
WorldGreetingServiceを実装したクラスを区別するためにアノテーションを追加します。
package com.example.project.service;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface English {
}
JapaneseGreetingServiceImpl.java(修正後)
@Componentを削除します。
package com.example.project.service;
public class JapaneseGreetingServiceImpl implements WorldGreetingService {
private String targetName;
public JapaneseGreetingServiceImpl(String targetName) {
this.targetName = targetName;
}
@Override
public String morningGreeting() {
return "おはよう、 " + targetName + "。";
}
@Override
public String afternoonGreeting() {
return "こんにちは、 " + targetName + "。";
}
@Override
public String eveningGreeting() {
return "こんばんは、 " + targetName + "。";
}
}
Japanese.java(新規作成)
WorldGreetingServiceを実装したクラスを区別するためにアノテーションを追加します。
package com.example.project.service;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Japanese {
}
app.properties(新規作成)
もともとbeams.xmlで設定した値をプロパティに切り出します。
greeting.name.english=Spring's World
greeting.name.japanese=Springの世界
AppConfig.java(新規作成)
これがbeams.xmlの代わりになるクラスです。
package com.example.project.config;
import com.example.project.service.English;
import com.example.project.service.EnglishGreetingServiceImpl;
import com.example.project.service.Japanese;
import com.example.project.service.JapaneseGreetingServiceImpl;
import com.example.project.service.WorldGreetingService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan(basePackages = "com.example.project")
@PropertySource("classpath:config/app.properties")
public class AppConfig {
@Value("${greeting.name.english}")
private String englishTargetName;
@Value("${greeting.name.japanese}")
private String japaneseTargetName;
@Bean
@English
public WorldGreetingService englishGreetingService() {
return new EnglishGreetingServiceImpl(englishTargetName);
}
@Bean
@Japanese
public WorldGreetingService japaneseGreetingService() {
return new JapaneseGreetingServiceImpl(japaneseTargetName);
}
}
HelloWorldImpl.java(修正後)
@Componentを追加します。@JapaneseアノテーションでJapaneseGreetingSerivceを指定してインジェクションしています。
package com.example.project.domain;
import com.example.project.service.Japanese;
import com.example.project.service.WorldGreetingService;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("helloworld")
public class HelloWorldImpl implements HelloWorld {
@Autowired
@Japanese
private WorldGreetingService greetingService;
@Override
public String expression() {
LocalDateTime dateTime = LocalDateTime.now();
int hour = dateTime.getHour();
String expression;
if (hour >= 4 && hour <= 9) {
expression = greetingService.morningGreeting();
} else if (hour >= 10 && hour <= 17) {
expression = greetingService.afternoonGreeting();
} else {
expression = greetingService.eveningGreeting();
}
return expression;
}
}
SampleApp.java(修正後)
「AnnotationConfigApplicationContext」により、クラスで定義した設定を読み込むようにしています。
package com.example.project;
import com.example.project.config.AppConfig;
import com.example.project.domain.HelloWorld;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SampleApp {
public static void main(String[] args) {
final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
AppConfig.class);
final HelloWorld helloWorld = (HelloWorld) context.getBean("helloworld");
System.out.println(helloWorld.expression());
context.close();
}
}
修正が完了したので実行してみましょう。
$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.SampleApp"
...
こんにちは、 Springの世界。
...
うまくいきました。これでbeans.xmlから完全にアノテーションに移行できました。
アノテーションのDIの使い方
最後に、アノテーションのDIのパターンを見ていきましょう。
フィールド・インジェクション
先程の作ったHelloWorldImpl.javaはフィールド・インジェクションを利用しています。
HelloWorldImpl.java(抜粋)
package com.example.project.domain;
...
@Component("helloworld")
public class HelloWorldImpl implements HelloWorld {
@Autowired
@Japanese
private WorldGreetingService greetingService;
...
}
この方法はフィールドに@Autowiredを指定するだけで利用できます。ただし、後述しますが、アンチパターンです。
コンストラクタ・インジェクション
続いて、コンストラクタを追加して、インジェクションするように修正しましょう。
HelloWorldImpl.java(修正後)
package com.example.project.domain;
import com.example.project.service.Japanese;
import com.example.project.service.WorldGreetingService;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("helloworld")
public class HelloWorldImpl implements HelloWorld {
private WorldGreetingService greetingService;
@Autowired
public HelloWorldImpl(@Japanese WorldGreetingService greetingService) {
this.greetingService = greetingService;
}
@Override
public String expression() {
LocalDateTime dateTime = LocalDateTime.now();
int hour = dateTime.getHour();
String expression;
if (hour >= 4 && hour <= 9) {
expression = greetingService.morningGreeting();
} else if (hour >= 10 && hour <= 17) {
expression = greetingService.afternoonGreeting();
} else {
expression = greetingService.eveningGreeting();
}
return expression;
}
}
実行してみましょう。
$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.SampleApp"
...
こんにちは、 Springの世界。
...
OKです。
セッター・インジェクション
コンストラクタを削除し、セッターを追加してインジェクションするように修正しましょう。
HelloWorldImpl.java(修正後)
package com.example.project.domain;
import com.example.project.service.English;
import com.example.project.service.WorldGreetingService;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("helloworld")
public class HelloWorldImpl implements HelloWorld {
private WorldGreetingService greetingService;
@Autowired
public void setGreetingService(@English WorldGreetingService greetingService) {
this.greetingService = greetingService;
}
@Override
public String expression() {
LocalDateTime dateTime = LocalDateTime.now();
int hour = dateTime.getHour();
String expression;
if (hour >= 4 && hour <= 9) {
expression = greetingService.morningGreeting();
} else if (hour >= 10 && hour <= 17) {
expression = greetingService.afternoonGreeting();
} else {
expression = greetingService.eveningGreeting();
}
return expression;
}
}
実行してみましょう。
$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.SampleApp"
...
Hello, Spring's World.
...
完璧ですね。
どちらを使うべきか?
基本的にはコンストラクタ・インジェクションを使うのが良いです。理由はXMLのDIの使い方で説明したことと同じです。また、フィールド・インジェクションは絶対に使うべきではありません。Springチームが公式に使わないことを勧めています。まともにテストした事がある人なら分かることですが、フィールド・インジェクションした箇所の単体テストをしようとした場合、リフレクションを使うはめになります。うんざりです。加えて、よく言われているのは、一つのクラスに多くのDIを気づかずに書いてしまい、Single Responsibility Principle(単一責務の原則)に知らないうちに違反していることがあります。要はコンストラクタやセッターはコーディングする時の良い意味で目印になっているという話です。結論としては、迷わずにコンストラクタ・インジェクションを使う、と覚えておきましょう。
最後に
いかがでしたか?SpringFrameworkを学習する時に最初に理解すべきなのはDIです。方法を整理して使いこなせるようにしましょう。それでは。
環境
- JDK: openjdk 10.0.1 2018-04-17
- Maven: Apache Maven 3.5.3
- SpringFramework: 5.0.7.RELEASE


コメントを残す