Casual Developers Note

エンジニアやデザイナー向けの技術情報・英語学習情報・海外留学情報・海外旅行情報を提供中。世界を旅して人生を楽しもう。

  • ホーム
  • 技術 Tips & Tutorials
  • 技術塾
  • ライフハック
  • 海外留学
  • 英語学習
  • コラム
  • お問い合わせ
現在の場所:ホーム / アーカイブdi

2018年7月20日 By KD コメントを書く

動かして分かる! SpringFrameworkのIoCの方法およびDIの使い方

動かして分かる! SpringFrameworkのIoCの方法およびDIの使い方

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」ファイルを作成します。

Ioc 1

「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」ファイルを移動します。

Ioc 2

メインクラス「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で必要なファイルを追加すると以下のようになります。

Ioc 3

それぞれのソースコードは以下のようになります。

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

カテゴリ : 技術 Tips & Tutorials タグ : di, ioc, java, maven, springframework

ブログ更新情報や海外の関連情報などを配信する無料メルマガ

Sponsored Links

About Author

KD

世界を旅し日本を愛するエンジニア。大学でコンピュータサイエンスの楽しさを学び、日本の大手IT企業で働く中で、新しい技術やスケールするビジネスが北米にある事に気づく。世界に挑戦するための最大の壁が英語であったため、フィリピン留学およびカナダ留学を経て英語を上達させた。現在は日本在住でエンジニアとして働きつつ、次の挑戦に備えて世界の動向を注視している。挑戦に終わりはない。このブログでは、エンジニアやデザイナー向けの技術情報から、海外に留学したい人向けの留学情報、海外に興味がある人向けの海外旅行情報など、有益な情報を提供しています。

https://casualdevelopers.com/

最近の投稿

  • 2020年JS周辺のバックエンド寄りの注目技術!ネクストNodeJSの「Deno」と分散型パッケージレジストリの「Entropic」の紹介

    2020年JS周辺のバックエンド寄りの注目技術!ネクストNodeJSの「Deno」と分散型パッケージレジストリの「Entropic」の紹介

    2020年1月13日
  • 今さら聞けないJavaによる関数型プログラミング入門 ~ラムダ式、ストリーム、関数型インターフェース~

    今さら聞けないJavaによる関数型プログラミング入門 ~ラムダ式、ストリーム、関数型インターフェース~

    2019年11月4日
  • ReactのためのEslintおよびPrettierの設定方法 ~Airbnb JavaScript Style Guideの適用~

    ReactのためのEslintおよびPrettierの設定方法 ~Airbnb JavaScript Style Guideの適用~

    2019年10月30日
  • BashからZshに移行する方法(Mac編)

    BashからZshに移行する方法(Mac編)

    2019年10月21日
  • Create React Appを使わないでゼロからReactの開発環境を構築する方法(Webpack/Docker編)

    Create React Appを使わないでゼロからReactの開発環境を構築する方法(Webpack/Docker編)

    2019年9月30日

カテゴリ

  • 技術 Tips & Tutorials (100)
  • 技術塾 (6)
  • ライフハック (26)
  • 海外留学 (12)
  • 英語学習 (3)
  • コラム (6)

アーカイブ

最高の学習のために

人気記事ランキング

  • MySQLで「ERROR 2003 (HY000): Can't connect to MySQL server」と怒られた時の対処法
    MySQLで「ERROR 2003 (HY000): Can't connect to MySQL server」と怒られた時の対処法
  • Expressで「Cannot set headers after they are sent to the client」と怒られた時の対処法
    Expressで「Cannot set headers after they are sent to the client」と怒られた時の対処法
  • SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
    SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
  • 爆速でJenkinsをマスターしよう(GitHubアカウント統合編) ~ JenkinsのGitHub Organizationの設定方法 ~
    爆速でJenkinsをマスターしよう(GitHubアカウント統合編) ~ JenkinsのGitHub Organizationの設定方法 ~
  • [tips][perl] Perlで文字コードをいい感じに処理する方法
    [tips][perl] Perlで文字コードをいい感じに処理する方法
  • FacebookログインのJavascriptのSDKをサクッと試す方法
    FacebookログインのJavascriptのSDKをサクッと試す方法
  • Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
    Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
  • SLF4JとLogbackによるJavaのロギング入門(SLF4J + Logback + Lombok)
    SLF4JとLogbackによるJavaのロギング入門(SLF4J + Logback + Lombok)
  • [tips][bat] バッチで明日の日付を計算する。
    [tips][bat] バッチで明日の日付を計算する。
  • PythonでWebスクレイピング入門(Scrapy+Selenium編)
    PythonでWebスクレイピング入門(Scrapy+Selenium編)

Bitcoin寄付 / BTC Donation

Bitcoinを寄付しよう

BTC
Select Payment Method
Personal Info

Donation Total: BTC 0.0010

このブログの運営のためにBitcoinでの寄付を募集しています。お気持ち程度の寄付を頂けると管理者の励みになります。

Bitcoin寄付について知りたい方はこちらの記事へ

ビットコイン取引ならここ

  • ホーム
  • 技術 Tips & Tutorials
  • 技術塾
  • ライフハック
  • 海外留学
  • 英語学習
  • コラム
  • サイトマップ
  • タグ一覧
  • プライバシーポリシー
  • お問い合わせ

Copyright © 2023 KD - Casual Developers Notes