Casual Developers Note

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

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

2018年9月17日 By KD コメントを書く

Springプロジェクトをマルチモジュールで構成する方法(Maven編)

Springプロジェクトをマルチモジュールで構成する方法(Maven編)

マルチモジュール化は機能のまとまりを明確化し、プロジェクト再利用性やメンテナンス性を高める良い方法です。今回は、Mavenでのマルチモジュール化の方法と、それをSpringプロジェクトに適用する方法を紹介します。

はじめに

マルチモジュール化とは、プロジェクト内に複数のプロジェクトを閉じ込めるようにプロジェクトを構成することです。MavenのPOMには親子関係を示す記述方法があり、それを利用することで複数のプロジェクトに機能分割しつつ、ビルドをまとめて実施することが可能です。当然汎用的な機能を分割しておけば他のプロジェクトに再利用できますし、プロジェクトの肥大化を防げるのでメンテナンスが容易になります。

それでは、Mavenによるマルチモジュール化の方法と、Springプロジェクトへの適用方法を見ていきましょう。

Springプロジェクトのマルチモジュール構成

前提

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

  • Java10
  • Maven3

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

サンプルのベースのプロジェクトを作る

まずはMavenでベースのプロジェクトを作りましょう。

$ mvn -B archetype:generate \
> -DarchetypeGroupId=org.apache.maven.archetypes \
> -DgroupId=com.example.project \
> -DartifactId=sample-multi-modules-app

マルチモジュール作成方法①IntelliJからモジュール追加する

IntelliJのIDEを使ってモジュールを追加しましょう。

まずIDEで「sample-multi-modules-app」を開き、「src」フォルダを削除します。

Ide 1

続いて、プロジェクトから「New -> Module」を選択します。

Ide 2

「Next」をクリックします。

Ide 3

モジュール名(今回は「core」)を入力して「Next」をクリックします。

Ide 4

「Finish」をクリックすれば完了です。

Ide 5

同様に「main」というモジュールも追加します。

そうすると以下のようになります。2つのモジュールが追加されており、POMファイルが3つになっています。

Ide 6

デフォルトで作成されたPOMファイルは以下のようになっています。

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>sample-multi-modules-app</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>core</module>
    <module>main</module>
  </modules>
  <name>sample-multi-modules-app</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

pom.xml(coreモジュール)

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>sample-multi-modules-app</artifactId>
    <groupId>com.example.project</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>core</artifactId>


</project>

pom.xml(mainモジュール)

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>sample-multi-modules-app</artifactId>
    <groupId>com.example.project</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>main</artifactId>


</project>

マルチモジュール作成方法②MavenコマンドからGenerateする

もちろん、Mavenコマンドからマルチモジュールを構成することが可能です。

$ cd sample-multi-modules-app2/
$ tree
.
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── example
    │               └── project
    │                   └── App.java
    └── test
        └── java
            └── com
                └── example
                    └── project
                        └── AppTest.java
$ rm -rf src/
$ tree
.
└── pom.xml
$ vi pom.xml
...
  <packaging>pom</packaging>
...
$ mvn -B archetype:generate \
> -DarchetypeGroupId=org.apache.maven.archetypes \
> -DgroupId=com.example.project.core \
> -DartifactId=core
$ mvn -B archetype:generate \
> -DarchetypeGroupId=org.apache.maven.archetypes \
> -DgroupId=com.example.project.main \
> -DartifactId=main
$ tree
.
├── core
│   ├── pom.xml
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── example
│       │               └── project
│       │                   └── core
│       │                       └── App.java
│       └── test
│           └── java
│               └── com
│                   └── example
│                       └── project
│                           └── core
│                               └── AppTest.java
├── main
│   ├── pom.xml
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── example
│       │               └── project
│       │                   └── main
│       │                       └── App.java
│       └── test
│           └── java
│               └── com
│                   └── example
│                       └── project
│                           └── main
│                               └── AppTest.java
└── pom.xml

ただし、IntelliJと違って自動でPOMファイルをうまく修正してくれないので、手動での修正が多くなります。

サンプルをSpringプロジェクトにする

サンプルを動く形に手直ししましょう。

IDEで以下の構成でファイルを作成します。

$ tree
.
├── core
│   ├── core.iml
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── com
│       │   │       └── example
│       │   │           └── project
│       │   │               └── core
│       │   │                   ├── AppConfig.java
│       │   │                   ├── ApplicationServerImpl.java
│       │   │                   └── Server.java
│       │   └── resources
│       │       └── logback.xml
│       └── test
│           └── java
├── main
│   ├── main.iml
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── com
│       │   │       └── example
│       │   │           └── project
│       │   │               └── main
│       │   │                   └── Main.java
│       │   └── resources
│       └── test
│           └── java
└── pom.xml

ソースコードは以下になります。マルチモジュール化したことで、POMを親子で構成している点がポイントです。

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>sample-multi-modules-app</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>core</module>
    <module>main</module>
  </modules>
  <name>sample-multi-modules-app</name>
  <url>http://maven.apache.org</url>

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

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
      </dependency>

      <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>
  </dependencyManagement>

  <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>

pom.xml(coreモジュール)

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>sample-multi-modules-app</artifactId>
    <groupId>com.example.project</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>core</artifactId>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
    </dependency>

    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
  </dependencies>


</project>

logback.xml(coreモジュール)

<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.project" level="DEBUG"/>
  <logger name="org.springframework" level="WARN"/>

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

Server.java(coreモジュール)

package com.example.project.core;

import lombok.Getter;

public interface Server {

  enum Status {
    STOPPED("stopped"), RUNNING("running");

    @Getter
    private String value;

    Status(String value) {
      this.value = value;
    }

  }

  public void run();

  public String status();

  public void stop();

}

ApplicationServerImpl.java(coreモジュール)

package com.example.project.core;

import org.springframework.stereotype.Component;

@Component("applicationServer")
public class ApplicationServerImpl implements Server {

  private Status status = Status.STOPPED;

  public void run() {
    status = Status.RUNNING;
  }

  public String status() {
    return status.getValue();
  }

  public void stop() {
    status = Status.STOPPED;
  }
}

AppConfig.java(coreモジュール)

package com.example.project.core;

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

@Configuration
@ComponentScan("com.example.project")
public class AppConfig {

}

pom.xml(mainモジュール)

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>sample-multi-modules-app</artifactId>
    <groupId>com.example.project</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>main</artifactId>

  <dependencies>
    <dependency>
      <groupId>com.example.project</groupId>
      <artifactId>core</artifactId>
      <version>${project.version}</version>
    </dependency>
  </dependencies>

</project>

Main.java(mainモジュール)

package com.example.project.main;

import com.example.project.core.AppConfig;
import com.example.project.core.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@Slf4j
public class Main {

  public static void main(String[] args) {

    ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

    Server server = (Server) ctx.getBean("applicationServer");
    server.run();
    log.info("Server is {}.", server.status());
    server.stop();
    log.info("Server is {}.", server.status());

    ctx.close();

  }
}

実行してみましょう。

$ mvn clean install -pl main -am
$ cd main/
$ mvn exec:java -Dexec.mainClass="com.example.project.main.Main"
2018-07-12 23:07:50,137 [com.example.project.main.Main.main()] [INFO ] com.example.project.main.Main = Server is running.
2018-07-12 23:07:50,141 [com.example.project.main.Main.main()] [INFO ] com.example.project.main.Main = Server is stopped.

正しく動作しました。これでSpringプロジェクトをマルチモジュール化できました。

最後に

いかがでしたか?これでMavenを使ってSpringプロジェクトをマルチモジュール化できるようになりましたね。ほとんどのプロジェクトで有効な方法ですので、実践していきましょう。では。

環境

  • JDK: openjdk 10.0.1 2018-04-17
  • Maven: Apache Maven 3.5.3
  • SpringFramework: 5.0.7.RELEASE

カテゴリ : 技術 Tips & Tutorials タグ : maven, multi-module, springframework

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」と怒られた時の対処法
  • Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
    Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
  • 爆速でJenkinsをマスターしよう(GitHubアカウント統合編) ~ JenkinsのGitHub Organizationの設定方法 ~
    爆速でJenkinsをマスターしよう(GitHubアカウント統合編) ~ JenkinsのGitHub Organizationの設定方法 ~
  • 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経由で送受信する方法
  • バンクーバー留学豆知識:バンクーバーのATMで日本の銀行のキャッシュカードを使ってお得にお金を引き出す方法
    バンクーバー留学豆知識:バンクーバーのATMで日本の銀行のキャッシュカードを使ってお得にお金を引き出す方法
  • [tips][perl] Perlで文字コードをいい感じに処理する方法
    [tips][perl] Perlで文字コードをいい感じに処理する方法
  • PythonでWebスクレイピング入門(Scrapy+Selenium編)
    PythonでWebスクレイピング入門(Scrapy+Selenium編)
  • Amazon EC2インスタンスにSSHできなくなった時の対処法
    Amazon EC2インスタンスにSSHできなくなった時の対処法
  • 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