Multi Vitamin & Mineral

Multi Vitamin & Mineral です。プログラムに関することを書いております。

Spring で DI をするにはどうするか? そのための3つのこと。

DI が何であるのかはここでは話はせずにですね、スパっと、 Spring で DI をするにはどうすれば良いのかだけ書きます。

DI をするための3つのこととは?

以下の3つが分かればいいでしょう。

  1. 自分が作ったクラス(POJO)を DI 対象にしてもらう方法
  2. DI 対象クラス(1. で作ったクラス)を利用する方法
  3. 1.、2. を有効にする方法

自分で作ったクラスを new しちゃったら DI じゃないんで、 Spring にインスタンス化( new )しておいてもらいたいわけです。 この、 Spring に「裏でヨロシクやっといて」というためには、我々は Spring に対して3つのことをする訳です。

  1. インスタンス化( new )してもらいたいクラスがどれかを教えて、
  2. インスタンス化( new )する指示をだします。
  3. っていう仕事をお願いすることになるのでヨロシクねと予め伝えます。

という3つのことができればおkです。 ということで、以下、3つの方法のお話です。

Spring で DI をする方法

1. 自分が作ったクラス(POJO)を DI 対象にしてもらう方法

DI の対象となるクラスには @Component アノテーションを付けます。

@Component // ←コレ
public class HogeBean {
  // ...
}

他にも、@Service, @Controller, @RestController, @Repository といったアノテーションも同じ働きをします。 いずれも @Component の拡張みたいなものです。 利用している機能次第で使えるようになります。

2. DI 対象クラス(1. で作ったクラス)を利用する方法

DI 対象のクラスを利用する(DIしてもらう)には @Autowired を付けてプロパティに保持します。

public class FugaLogic {
  @Autowired // ←コレ
  HogeBean hogeBean; // ←プロパティとして置いておきましょう。

  public void someMethod() {
    // hogeBean をインスタンス化せず使える。裏で Spring が new してくれている。
  }
}

3. 1.、2. を有効にする方法

main メソッドなど、エントリーポイントがあるようなクラスには @SpringBootApplication を付けます。 これで作成したクラス(上記の例だと HogeBean)を自動的に探してくれるようになり、また、 DI (裏で勝手に new)してもらえるようになります。

@SpringBootApplication // ←コレ
public class App {
  public static void main(String[] args) {
    // ...
  }
}

以上! これだけ知っていれば取り敢えず使えるはず! 閉店! ガラガラ! はい、あとはおまけ!

おまけ

ここからはもちっとちゃんと DI を知ろうという企画です。

サンプルアプリで確かめる

適当にプログラムを作ったので、中身を見ていきましょう。

spring_di_sample というプロジェクト名、 edu.self というパッケージ名で、サンプルアプリを作ります。 とりま、 Maven でプロジェクトを作って、と。

$ mvn -B archetype:generate -DgroupId=edu.self -DartifactId=spring_di_sample -Dversion=1.0.0-SNAPSHOT -DarchetypeArtifactId=maven-archetype-quickstart

プロジェクト直下にある pom.xml に Spring を使う設定を書いておきます。

<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>edu.self</groupId>
  <artifactId>my_first_spring_boot_di</artifactId>
  <packaging>jar</packaging>
  <version>1.0.0-SNAPSHOT</version>
  <name>my_first_spring_boot_di</name>
  <url>http://maven.apache.org</url>
  <!-- Spring Boot の利用を宣言し、バージョンを指定【追加】 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.1.RELEASE</version>
  </parent>
  <dependencies>
    <!-- Spring Boot のアプリケーションライブラリの利用を指定【追加】 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <!-- Spring Boot の ビルド用 Maven プラグイン【追加】 -->
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  <properties>
    <!-- Java バージョン指定【追加】 -->
    <java.version>1.8</java.version>
  </properties>
</project>

下記のようなパッケージ構成のアプリを作りました。

spring_di_sample
+- edu.self
    +- bean
    |  +- BigLight.java
    |  +- Light.java
    |  +- SmallLight.java
    +- config
    |  +- AppConfig.java
    +- AutoApp.java
    +- ManualApp.java

あ、もちろん STS とかで GUI で作ってもいいですよ。

上記の構成でプロジェクトが作られた、という前提で、以下、読んでください。

Spring での Bean とは?

以降、 DI のお話の中で出てくる Bean という用語について先に言及しておきます。

Spring では、 DI コンテナに管理させたい実装は Bean と呼ばれています。 Bean は、ただのクラスでもよいですし、インターフェースとその実装クラスでもよいです。 Bean として扱うクラスは、いわゆる POJO でよいです。普通のクラスでOK。

普通のクラスを作って、DIコンテナに登録しておきます。利用したいときは DI コンテナから取得して使います。

Spring はドラえもんなのだ

DI コンテナが四次元ポケットで、ひみつ道具が Bean です。 私たちは、 Bean というひみつ道具をせっせと作って、四次元ポケットに放り込んでおくわけです。 利用するときには、四次元ポケットから取り出します。取り出すのはドラえもん(= Spring)がやってくれます。

かえって分かりにくくなったら、、、すみません。。。

DI コンテナへの登録は自動でも、手動でも行えます。 普通は自動で使うものですが、今回は両方見ておきましょう。

DI コンテナへの自動登録と、自動取得

(1) Bean としてDIコンテナに登録するクラスを作ります。普通のクラス(POJO)です。 ただし、自動で Bean として認識してもらえる用に印を付けておく 必要があります。 その印が @Component アノテーションです。
(※このアノテーションには他にもいくつか種類があります。)
(※ひみつ道具という印になります。)

(2) 実行するクラスには、「Bean を探してね」という宣言(@ComponentScan アノテーション)を付けます。 取得したい Bean は @Autowired という指定とともに、クラスのプロパティとして持っておきます。 後は、自動で @Autowired が付いたクラスをインスタンス化してくれます。
(※ひみつ道具を使うのはのび太ですが、裏でドラえもんがひみつ道具を探して四次元ポケットから取り出して渡してくれるまでをやってくれます。)

以下、AutoApp クラスで、CommandLineRunner インターフェースを実装していますが、今回のようにコマンドラインから実行するサンプルアプリで、 Spring を起動しやすくするもの、くらいに思ってください。今回は深くは言及しません。

// (1) Bean として登録するクラスを用意します。
@Component // Bean として認識してもらう為の印です。
public class SmallLight {
  // 動作確認用のメソッドです。指定数だけ光が強くなります。
  public String lighten(int intensity) {
    String result = "";
    for (int i = 0; i < intensity; i++) {
      result += "すごく、";
    }
    return result + "小さくなった!";
  }
}

// (2) 実行クラスです。
@EnableAutoConfiguration
@ComponentScan // 配下のパッケージから Bean を探してくれという Spring に対する指示です。
public class AutoApp implements CommandLineRunner {
  // Spring に DI してもらいます。
  // SmallLight クラスを探してもらって、インスタンス化(new する)したものが自動でセットされます。
  @Autowired
  SmallLight smallLight;

  public static void main(String[] args) {
    // SpringApplication.run は、 Spring アプリケーションを起動します。
    // CommandLineRunner を実装していると、
    // 引数のクラス(このクラスです)の run メソッドを呼んでくれます。
    SpringApplication.run(AutoApp.class, args);
  }

  @Override
  public void run(String... arg0) throws Exception {
    // smallLight は自分でインスタンス化(new を)していません。
    // でも、裏で Spring がやってくれるので、インスタンス化された体で、
    // メソッドを呼び出すことができます。
    System.out.println(smallLight.lighten(3));
  }
}

出力結果は下記のようになります。

すごく、すごく、すごく、小さくなった!

DI コンテナへの手動登録と、手動取得

自動登録&自動取得では、 @ComponentScan アノテーションで登録、 @Autowired アノテーションで取得をやってくれました。 この2つのことを手動でやると以下のようになります。

(1) Bean としてDIコンテナに登録するクラスを作ります。普通のクラス(POJO)です。

(2) この Bean にするためには、設定クラスを用意して、さらに Bean を返すメソッドを作り、 @Bean アノテーションを付けれます。(設定クラスには @Configuration アノテーションが必要になります。)

(3) 実行するクラスには、「設定クラスを使うよ」という宣言(@Import アノテーション)を加えた上で、 Bean を取得してあげるという寸法になります。

まずは、Bean にタダのクラスを登録した例。

// (1) Bean として登録するクラスを用意します。
public class SmallLight {
  // 動作確認用のメソッドです。指定数だけ光が強くなります。
  public String lighten(int intensity) {
    String result = "";
    for (int i = 0; i < intensity; i++) {
      result += "すごく、";
    }
    return result + "小さくなった!";
  }
}

// (2)設定クラスです。
@Configuration // 設定をするクラスであることを示します。
public class AppConfig {
  @Bean // DIコンテナに管理させます。
  SmallLight smallLight() { // メソッド名がBean名になります。
    return new SmallLight();
  }
}

// (3) 実行クラスです。
@EnableAutoConfiguration // Spring の各種設定を使うという指定です。
@Import(AppConfig.class) // (2) の読み込む設定クラスを指定します。
public class ManualApp {
  public static void main(String[] args) {
    // SpringApplication.run は、 Spring アプリケーションを起動します。
    // 戻り値の ConfigurableApplicationContext から Bean を取得できます。
    try (ConfigurableApplicationContext context
        = SpringApplication.run(ManualApp.class, args)) {
      // (1) のクラスをクラス名を使って取得します。
      SmallLight smallLight = context.getBean(SmallLight.class);
      System.out.println(smallLight.lighten(3));
    }
  }
}

出力結果は下記のようになります。

すごく、すごく、すごく、小さくなった!

次、ちょっとおまけ。インターフェースを使った例です。

// (1) Bean として登録するインターフェースを用意します。
public interface Light {
  String lighten(int intensity);
}

// (1) Bean として登録する実体です。
public class BigLight implements Light {
  @Override
  public String lighten(int intensity) {
    String result = "";
    for (int i = 0; i < intensity; i++) {
      result += "すごく、";
    }
    return result + "大きくなった!";
  }
}

// (2)設定クラスです。
@Configuration
public class AppConfig {
  // Light インターフェースに BigLight クラスを結びつけています。
  // ライトといえばビッグライト。例外は認めません! という設定。
  @Bean // DIコンテナに管理させます。
  Light light() { // メソッド名がBean名になります。
    return new BigLight();
  }
}

// (3) 実行クラスです。
@EnableAutoConfiguration
@Import(AppConfig.class)
public class ManualApp {
  public static void main(String[] args) {
    try (ConfigurableApplicationContext context
        = SpringApplication.run(ManualApp.class, args)) {
      // ライト(Light)を取得したら...
      Light light = context.getBean(Light.class);
      // ビッグライト(BigLight)が光ります。設定クラスの(2)でそう設定したからです。
      System.out.println(light.lighten(3));
    }
  }
}

出力結果は下記のようになります。

すごく、すごく、すごく、大きくなった!

まとめ

要するに、 @Component で Bean を作って、 @Autowired 使って利用すればOKということです。 エントリーポイントあたりに @ComponentScan を書いておくと、自動で探して DI してくれます。

もし、 DI を手動でやるなら、 @Configuration を付けた設定クラスを作る必要が出てきます。 その中でひとつひとつ @Bean を付けたメソッドとして用意します。 エントリーポイントでは @ComponentScan じゃなくて @Import で設定クラスを指定すれば良いです。

逆に言うと、これらのチマチマした設定を @ComponentScan でやってくれるんだから使わない手はないっしょ、ということです。

@SpringBootApplication アノテーションについて

最後に、 @SpringBootApplication アノテーションについて。

STS でプロジェクトを自動生成すると、エントリーポイントとなるクラスには @SpringBootApplication が付いています。

これは何でしょうね?

実は、@Configuration, @EnableAutoConfiguration and @ComponentScan というよく使われるアノテーションをまとめたものでした。 以下の記事に書かれています。

docs.spring.io

3つの機能が備わっているのだという認識をしつつ、シンプルに、 @SpringBootApplication を使うのが良いでしょうね。