Casual Developers Note

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

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

2019年11月4日 By KD コメントを書く

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

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

Java8からラムダ式による関数型プログラミングが記述できるようになりました。今回は、ムーブメントに乗り遅れた人向けに、改めてJavaで関数型プログラミングの基礎を紹介します。

はじめに

近年は関数型プログラミングが新しいパラダイムシフトとなっており、あらゆるプログラミング言語で関数型プログラミングをサポートしています。ここで言う関数型プログラミングとは、あくまでラムダ式による新しいコーディングのやり方の事です。モナド、カリー化、遅延評価、高階関数などのそもそもの関数型言語の概念を知りたければHaskellを学ぶのが一番だと言われていますが、今回はそういう話は扱いません。純粋にJavaでラムダ(Lambda)式、ストリーム(Stream)、関数型インターフェース(Functional Interfaces)を使ってプログラミングしたい人向けです。

それでは、Javaで関数型プログラミングに入門していきましょう。

ストリーム(Stream)を使ってみよう

java.util.streamのパッケージで基本的なものを使ってみましょう。

forEach

「forEach」でfor文を使わなくても流れるようにループ処理を記述できます。

    var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);

    // 基本的な書き方
    numbers.stream()
      .forEach(number -> System.out.println(number));

    // メソッド参照で簡素に書ける
    numbers.stream()
      .forEach(System.out::println);

filter

「filter」は条件に合うデータだけになるようにフィルターをかけます。

    var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);

    // 偶数だけフィルターする
    numbers.stream()
      .filter(number -> number % 2 == 0)
      .forEach(System.out::println);

map

「map」はそれぞれのデータに共通の処理を記述できます。

    var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);

    // 全ての数字を2乗する
    numbers.stream()
      .map(number -> number * number)
      .forEach(System.out::println);

reduce

「reduce」はデータをまとめます。

    var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);

    // 合計を出す
    var sum = numbers.stream()
      .reduce(0, (x, y) -> x + y);
    // => 159

    // 奇数を3乗して足す
    var result = numbers.stream()
      .filter(number -> number % 2 != 0)
      .map(number -> number * number * number)
      .reduce(0, Integer::sum);
    // => 45787

collect

「collect」はストリームを再利用できる型に変換します。

    var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);

    // それぞれ2乗したデータをListで返す
    var squaredNumbers = numbers.stream()
      .map(number -> 2 * number)
      .collect(Collectors.toList());
    // => [36, 8, 44, 14, 62, 2, 24, 50, 72, 6]

takeWhile

「takeWhile」は、ストリームの最初から条件に合うものを抽出していき、条件が合わなかったところまでの範囲で取り出します。

    var numbers = List.of(3, 6, 9, 12, 15, 16, 20, 24, 28, 36, 33, 36, 39, 42, 45);

    // 3の倍数をリストの最初からそれ以外がでるまでの範囲で取り出す
    var firstTripleNumbers = numbers.stream()
      .takeWhile(number -> number % 3 == 0)
      .collect(Collectors.toList());
    // => [3, 6, 9, 12, 15]

    // 3の倍数を全て取り出す
    var tripleNumbers = numbers.stream()
      .filter(number -> number % 3 == 0)
      .collect(Collectors.toList());
    // => [3, 6, 9, 12, 15, 24, 36, 33, 36, 39, 42, 45]

distinct

「distinct」は重複を省きます。

    var numbers = List.of(10, 3, 22, 3, 22, 10, 22, 3, 3, 3);

    var distinctNumbers = numbers.stream()
      .distinct()
      .collect(Collectors.toList());
    // => [10, 3, 22]

sorted

「sorted」はソートします。「Comparator」を渡すことで、ソート方法を指定できます。

    var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);

    // 昇順
    var sortedNumbers = numbers.stream()
      .sorted()
      .collect(Collectors.toList());
    // => [1, 3, 4, 7, 12, 18, 22, 25, 31, 36]

    // 昇順
    var sortedNumbers2 = numbers.stream()
      .sorted(Comparator.naturalOrder())
      .collect(Collectors.toList());
    // => [1, 3, 4, 7, 12, 18, 22, 25, 31, 36]

    // 降順
    var reversedNumbers = numbers.stream()
      .sorted(Comparator.reverseOrder())
      .collect(Collectors.toList());
    // => [36, 31, 25, 22, 18, 12, 7, 4, 3, 1]

flatMap

「flatMap」はストリームのデータをフラットにします。

    var names = List
      .of("Keid", "Steve Jobs", "Jeff Bezos", "Larry Page", "Bill Gates", "Mark Zuckerberg");

    var list = names.stream()
      .map(str -> str.split(" ")) // 性と名を分けて
      .flatMap(Arrays::stream) // フラットな1次元配列にして
      .sorted() // アルファベット順にソートして
      .collect(Collectors.toList()); // リストにして返す
    // => [Bezos, Bill, Gates, Jeff, Jobs, Keid, Larry, Mark, Page, Steve, Zuckerberg]

range/rangeClosed

「range」と「rangeClosed」で指定した範囲の数字を扱えます。

    var sum1 = IntStream.range(1, 10).sum(); // => 45
    var sum2 = IntStream.rangeClosed(1, 10).sum(); // => 55
    var bigSum = LongStream.range(1, 100000000).parallel().mapToObj(BigInteger::valueOf)
      .reduce(BigInteger.ONE, BigInteger::add); // => 4999999950000063

「parallel」はマルチスレッドで並列処理させます。

iterate

「iterate」は繰り返し処理を記述できます。

    var sum = IntStream.iterate(1, n -> n + 3).limit(5).peek(System.out::println).sum();
    // => 1
    // => 4
    // => 7
    // => 10
    // => 13
    System.out.println("total:" + sum);
    // => total:35

「peek」はストリームの途中の値を抜き出せます。ストリームのデバッグに便利です。

allMatch/anyMatch/noneMatch

「allMatch」は、全てのデータが条件に合っているかどうか、「anyMatch」は、データのどれかが条件に合っているかどうか、「noneMatch」は、全てのデータが条件に合っていないかどうか、を判定します。

    var names = List
      .of("Keid", "Steve Jobs", "Jeff Bezos", "Larry Page", "Bill Gates", "Mark Zuckerberg");

    System.out.println(names.stream().allMatch(name -> name.length() >= 4)); // => true
    System.out.println(names.stream().allMatch(name -> name.length() >= 10)); // => false

    System.out.println(names.stream().anyMatch(name -> name.length() == 4)); // => true
    System.out.println(names.stream().anyMatch(name -> name.length() > 15)); // => false

    System.out.println(names.stream().noneMatch(name -> name.length() > 15)); // => true
    System.out.println(names.stream().noneMatch(name -> name.length() == 10)); // => false

関数型インターフェース(Functional Interfaces)を知ろう

java.util.functionのパッケージにあるLambda式やメソッド参照で使う型を見ていきましょう。

Predicate

「Predicate」は、1つの引数を与えて、booleanの値を返します。

    var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);

    // 奇数を判定するPredicate
    Predicate<Integer> isOdd = x -> x % 2 != 0;
    var oddNumbers = numbers.stream()
      .filter(isOdd)
      .collect(Collectors.toList());
    // => [7, 31, 1, 25, 3]

Function

「Function」は、1つの引数を与えて、1つの値を返します。

    // 倍にするFunction
    Function<Integer, Integer> doubleIt = x -> x + x;
    var doubleNumbers = numbers.stream()
      .map(doubleIt)
      .collect(Collectors.toList());
    // => [36, 8, 44, 14, 62, 2, 24, 50, 72, 6]

Consumer

「Consumer」は、1つの引数を与えて、voidで処理します。

    // 標準出力をするConsumer
    Consumer<Integer> print = x -> System.out.println(x);
    numbers.stream()
      .filter(isOdd)
      .map(doubleIt)
      .forEach(print);
    // => 14
    // => 62
    // => 2
    // => 50
    // => 6

BinaryOperator

「BinaryOperator」は、2つの引数を与えて、1つの値を返します。

    // 足し算をするBinaryOperator
    BinaryOperator<Integer> sum = (x, y) -> x + y;
    var oddSum = numbers.stream()
      .filter(isOdd)
      .reduce(sum);
    // => Optional[67]

Supplier

「Supplier」は、引数を与えずに、1つの値を返します。

    // 0〜99までの乱数を返すSupplier
    Supplier<Integer> randomInteger = () -> {
      Random random = new Random();
      return random.nextInt(100);
    };
    var randomNumber = randomInteger.get();

UnaryOperator

「UnaryOperator」は、1つの引数を与えて、1つの値を返します。Functionとの違いは引数と戻り値の型が同じというところです。

    // 10倍にするUnaryOperator
    UnaryOperator<Integer> multiplyByTen = x -> 10 * x;
    var tenfoldEvenNumbers = numbers.stream()
      .filter(x -> x % 2 == 0)
      .map(multiplyByTen)
      .collect(Collectors.toList());
    // => [180, 40, 220, 120, 360]

BiPredicate/BiFunction/BiConsumer

「BiPredicate」は、2つの引数を与えて、booleanの値を返します。「BiFunction」は、2つの引数を与えて、1つの値を返します。「BiConsumer」は、2つの引数を与えて、voidで処理します。

    // Idが100以上かつNameにD.を含むかどうかを判定するBiPredicate
    BiPredicate<Integer, String> isProtagonist = (id, name) -> {
      return id >= 100 && name.contains("D.");
    };
    // IdとNameを結合するBiFunction
    BiFunction<Integer, String, String> joinIdAndName = (id, name) -> {
      return id + ":" + name;
    };
    // IdとNameを結合して標準出力するBiConsumer
    BiConsumer<Integer, String> printIdAndName = (id, name) -> {
      System.out.println(id + ":" + name);
    };

    class Hero {

      private int id;
      private String name;

      public Hero(int id, String name) {
        this.id = id;
        this.name = name;
      }

      public int getId() {
        return id;
      }

      public String getName() {
        return name;
      }
    }

    var protagonistName = List.of(
      new Hero(200, "Edward Teach"),
      new Hero(30, "Rocks D. Shanks"),
      new Hero(100, "Monkey D. Luffy")
    ).stream()
      .peek(hero -> printIdAndName.accept(hero.id, hero.name))
      .filter(hero -> isProtagonist.test(hero.id, hero.name))
      .map(hero -> joinIdAndName.apply(hero.id, hero.name))
      .findFirst();
    // => 200:Edward Teach
    // => 30:Rocks D. Shanks
    // => 100:Monkey D. Luffy
    System.out
      .println("The protagonist in One Piece is " + protagonistName.orElse("Nothing") + "!");
    // => The protagonist in One Piece is 100:Monkey D. Luffy!

以上、ここまで分かれば、リファレンスだけで使いこなせるでしょう。

おまけ

ストリームによるファイル操作

「Files.lines」を使うことで、ファイルを読み込んでストリームで扱えます。

以下のCSVファイルを準備して読み込んでみます。

file.csv

Keid,Steve Jobs,Jeff Bezos
Larry Page,Bill Gates,Mark Zuckerberg

行ごとに読み込んで、カンマで分割し、それぞれを出力してみます。

    Files.lines(Paths.get("file.csv"))
      .map(str -> str.split((",")))
      .flatMap(Arrays::stream)
      .forEach(System.out::println);
    // => Keid
    // => Steve Jobs
    // => Jeff Bezos
    // => Larry Page
    // => Bill Gates
    // => Mark Zuckerberg

ファイル操作も簡単になりましたね。

最後に

いかがでしたか?これでJavaで関数型プログラミングが記述できるようになったのではないでしょうか。あとは、java.util.streamおよびjava.util.functionのパッケージを見ていろいろ試してみると良いでしょう。では。

環境

  • Java: 11.0.4

カテゴリ : 技術 Tips & Tutorials タグ : functional-interfaces, functional-programming, java, lambda-expressions

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

SLF4JとLogbackによるJavaのロギング入門(SLF4J + Logback + Lombok)

SLF4JとLogbackによるJavaのロギング入門(SLF4J + Logback + Lombok)

アプリケーションにおいてロギング処理は無くてはならない必需品です。現在ではログは故障の解析はもちろんのこと、セキュリティチェックやユーザの行動分析など多岐に渡って利用されています。今回はJavaによるロギング方法として、SLF4JとLogback、Lombokを使った方法を紹介します。

はじめに

Javaのロギングは、一昔前はCommons-loggingとLog4jによる方法が一般的でしたが、現在ではSLF4J+Logbackに取って代わりました。ベースは全てLog4jなので、乗り換える人も特に苦はありません。今回は、SLF4J+Logbackによるロギング方法を紹介します。ついでに、Lombokで簡潔に記述してみましょう。

SLF4Jとは?

SLF4Jは、Javaのロギング用のファサードAPIです。一昔前のCommons-loggingに該当します。役割としては、ライブラリが使っているロギングの実装が異なっていた場合に、インターフェースを統一するためのものです。

Logbackとは?

Logbackは、Javaのロギングの実装です。一昔前のLog4Jに該当します。SLF4Jと一緒に使う前提で作られており、特徴としてはアダプタによるログの横取りにより、他のロギングライブラリを使っているライブラリとログ出力を統一できます。

Lombokとは?

Lombokとは、一般的なボイラープレートをアノテーションで省略できるようにするライブラリです。例えば、よく書くゲッターやセッターが必要な場面では、@Getterや@Setter、@Dataのアノテーションを書くだけで実現できます。そして、SLF4J用に@Slf4jのアノテーションが提供されているので、今回利用します。

SLF4JとLogbackによるロギング

前提

以下がインストールされている前提で進めます。

  • Java10
  • Maven3

細かいバージョンは「環境」を参照して下さい。

サンプルアプリケーションを作る

それでは、雛形を作りましょう。

$ mvn -B archetype:generate \
> -DarchetypeGroupId=org.apache.maven.archetypes \
> -DgroupId=com.example.project \
> -DartifactId=logging-sample-app   
$ cd logging-sample-app/
$ rm src/main/java/com/example/project/App.java
$ rm src/test/java/com/example/project/AppTest.java
$ touch src/main/java/com/example/project/Main.java
$ mkdir -p src/main/resources
$ touch src/main/resources/logback.xml
$ tree src/main/
src/main/
├── java
│   └── com
│       └── example
│           └── project
│               └── Main.java
└── resources
    └── logback.xml
$ tree
.
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── project
│   │   │               └── Main.java
│   │   └── resources
│   │       └── logback.xml
...

できました。

標準出力

それでは、標準出力にログを出力してみましょう。

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>logging-sample-app</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>logging-sample-app</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>10</java.version>
    <logback.version>1.2.3</logback.version>
    <lombok-version>1.18.0</lombok-version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>${logback.version}</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok-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>

Main.java

package com.example.project;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {

  private final static Logger log = LoggerFactory.getLogger(Main.class);

  public static void main(String[] args) {
    log.info("{} starting...", "My logging process");

    log.trace("Tracing...");
    log.debug("Debugging...");
    log.warn("Warning...");
    log.error("Error handling...");

    log.info("{} done.", "My logging process");
  }

}

logback.xml

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <pattern>%date [%thread] [%-5level] %logger{40} = %message%n</pattern>
    </encoder>
  </appender>

  <logger name="com.example" level="DEBUG"/>

  <root level="INFO">
    <appender-ref ref="STDOUT"/>
  </root>
</configuration>

実行してみましょう。

$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.Main"
...
2018-07-11 22:21:34,057 [com.example.project.Main.main()] [INFO ] com.example.project.Main = My logging process starting...
2018-07-11 22:21:34,060 [com.example.project.Main.main()] [DEBUG] com.example.project.Main = Debugging...
2018-07-11 22:21:34,061 [com.example.project.Main.main()] [WARN ] com.example.project.Main = Warning...
2018-07-11 22:21:34,061 [com.example.project.Main.main()] [ERROR] com.example.project.Main = Error handling...
2018-07-11 22:21:34,062 [com.example.project.Main.main()] [INFO ] com.example.project.Main = My logging process done.
...

エラーレベルをDEBUGにしたので、TRACE以外の全てのレベルのログが出力されました。

ファイル出力

次にファイルに出力してみましょう。アペンダーはRollingFileAppenderを、ポリシーはTimeBasedRollingPolicyを使います。詳しくは公式ドキュメントを参照して下さい。

logback.xml

<configuration>
  <property name="encoding" value="UTF-8"/>
  <property name="logPath" value="logs/"/>
  <property name="logFormat" value="%date [%thread] [%-5level] %logger{40} = %message%n"/>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <pattern>${logFormat}</pattern>
    </encoder>
  </appender>

  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${logPath}app.log</file>

    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${logPath}app.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <charset>${encoding}</charset>
      <pattern>${logFormat}</pattern>
      <outputPatternAsHeader>true</outputPatternAsHeader>
    </encoder>
  </appender>

  <logger name="com.example" level="DEBUG"/>

  <root level="INFO">
    <appender-ref ref="STDOUT"/>
    <appender-ref ref="FILE"/>
  </root>
</configuration>

実行してみましょう。

$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.Main"
...
$ tree logs/
logs/
└── app.log
$ cat logs/app.log
#logback.classic pattern: %date [%thread] [%-5level] %logger{40} = %message%n
2018-07-11 22:21:34,057 [com.example.project.Main.main()] [INFO ] com.example.project.Main = My logging process starting...
2018-07-11 22:21:34,060 [com.example.project.Main.main()] [DEBUG] com.example.project.Main = Debugging...
2018-07-11 22:21:34,061 [com.example.project.Main.main()] [WARN ] com.example.project.Main = Warning...
2018-07-11 22:21:34,061 [com.example.project.Main.main()] [ERROR] com.example.project.Main = Error handling...
2018-07-11 22:21:34,062 [com.example.project.Main.main()] [INFO ] com.example.project.Main = My logging process done.

ファイルにログが出力されました。

Lombokによる記述の簡略化

すでにPOMでLombokを追加してあるので、Main.javaで使ってみましょう。

Main.java

@Slf4jを使うことで、ロガーの定義を省略できます。

package com.example.project;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Main {

  public static void main(String[] args) {
    log.info("{} starting...", "My logging process");

    log.trace("Tracing...");
    log.debug("Debugging...");
    log.warn("Warning...");
    log.error("Error handling...");

    log.info("{} done.", "My logging process");
  }

}

実行してみましょう。

$ mvn clean install
$ mvn exec:java -Dexec.mainClass="com.example.project.Main"
...
2018-07-11 22:35:34,612 [com.example.project.Main.main()] [INFO ] com.example.project.Main = My logging process starting...
2018-07-11 22:35:34,616 [com.example.project.Main.main()] [DEBUG] com.example.project.Main = Debugging...
2018-07-11 22:35:34,617 [com.example.project.Main.main()] [WARN ] com.example.project.Main = Warning...
2018-07-11 22:35:34,617 [com.example.project.Main.main()] [ERROR] com.example.project.Main = Error handling...
2018-07-11 22:35:34,617 [com.example.project.Main.main()] [INFO ] com.example.project.Main = My logging process done.
...

問題なく動きますね。

最後に

いかがでしたか?これでJavaの基本的なロギングの方法は理解できたと思います。何かアプリケーションを作る時はSystem.outを使わずにSLF4JとLogbackを使いましょう。では。

環境

  • JDK: openjdk 10.0.1 2018-04-17
  • Maven: Apache Maven 3.5.3
  • Logback-classic: 1.2.3
  • Lombok: 1.18.0

カテゴリ : 技術 Tips & Tutorials タグ : java, logback, logging, lombok, slf4j

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

  • 1
  • 2
  • 次のページ »

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

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」と怒られた時の対処法
  • Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
    Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
  • SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
    SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
  • バンクーバー留学豆知識:バンクーバーのATMで日本の銀行のキャッシュカードを使ってお得にお金を引き出す方法
    バンクーバー留学豆知識:バンクーバーのATMで日本の銀行のキャッシュカードを使ってお得にお金を引き出す方法
  • Amazon EC2インスタンスにSSHできなくなった時の対処法
    Amazon EC2インスタンスにSSHできなくなった時の対処法
  • Expressで「Cannot set headers after they are sent to the client」と怒られた時の対処法
    Expressで「Cannot set headers after they are sent to the client」と怒られた時の対処法
  • TumblrからWordPressにブログ移転する最適な方法
    TumblrからWordPressにブログ移転する最適な方法
  • Go言語のためのVisual Studio Codeの設定方法
    Go言語のためのVisual Studio Codeの設定方法
  • バンクーバー留学豆知識:水道水は美味しく飲めるBritaさえあれば!
    バンクーバー留学豆知識:水道水は美味しく飲めるBritaさえあれば!
  • SpringBootのProfile毎にプロパティを使い分ける3つの方法
    SpringBootのProfile毎にプロパティを使い分ける3つの方法

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