Spring学習ノート - IoC、コンテナ、Beanのライフサイクル、DI

1. 学習概要

  • Springの利点:
    • 開発の簡素化
      • IoC(制御の反転)
      • AOP(アスペクト指向プログラミング)
        • トランザクション処理
    • フレームワーク統合
      • MyBatis
      • MyBatis-plus
      • Struts
      • Struts2
      • Hibernate
      • ......
  • 主な学習内容
    • IoC
    • MyBatisとの統合(IoCの具体的な応用)
    • AOP
    • 宣言的トランザクション(AOPの具体的な応用)
  • 学習の重点
    • Springの思想
    • Springの基本的な操作
    • ケーススタディの練習

2. Springの関連概念

2.1 Springの概要

公式サイト: https://spring.io

2.1.1 Springができること

  • Web
  • マイクロサービス
  • 分散システム
  • ......

2.1.2 重点学習内容

  • Spring Framework: Springで最も古く、最も核心的な技術であり、他のすべての技術の基礎です。
  • SpringBoot: 開発を簡素化し、SpringBootは簡素化された基盤上でより迅速な開発を支援するために存在します。
  • SpringCloud: 分散システムにおけるマイクロサービスアーキテクチャの開発に使用されます。
  • SpringData、SpringSecurityなども現在は人気のある技術です。

2.1.3 Springの発展史

時間の経過とともにバージョンは継続的に更新・保守され、現在最新はSpring5です。

  • Spring1.0は純粋な設定ファイル開発
  • Spring2.0は開発を簡素化するためにアノテーション開発を導入し、この時点では設定ファイルとアノテーションの組み合わせの開発方式でした
  • Spring3.0では純粋なアノテーション開発が可能になり、開発効率が大幅に向上しました
  • Spring4.0はJDKのバージョンアップに応じて一部APIを調整しました
  • Spring5.0はJDK8を全面的にサポートし、現在Springの最新は5シリーズです

2.2 Springのシステムアーキテクチャ

2.2.1 システムアーキテクチャと図示

  • 説明
    • コア層
      • Core Container: コアコンテナ、このモジュールはSpringの最も核心的なモジュールであり、他のすべてのモジュールがこのモジュールに依存しています
    • AOP層
      • AOP: アスペクト指向プログラミング、これはコア層コンテナに依存しており、元のコードを変更せずに機能を強化することを目的とします
      • Aspects: AOPは思想であり、AspectsはAOP思想の具体的な実装です
    • データ層
      • Data Access: データアクセス、Springファミリにはデータアクセスの具体的な実装技術があります
      • Data Integration: データ統合、SpringはMyBatisのような他のデータ層ソリューションとの統合をサポートしています
      • Transactions: トランザクション、Springのトランザクション管理はSpring AOPの具体的な実装であり、後続の内容で詳しく説明します
    • Web層
      • このレイヤーの内容はSpringMVCフレームワークの部分で詳しく説明します
    • Test層
      • Springは主にJunitを統合して単体テストと統合テストを実行します

2.2.2 コース学習ルート

  1. コアコンテナ
    • コア概念(IoC / DI)
    • コンテナの基本操作
  2. AOP
    • コア概念
    • AOPの基本操作
    • AOPの使用開発
  3. トランザクション
    • トランザクションの実用的な開発
  4. 統合
    • データ層技術MyBatisとの統合
  5. ファミリー
    • SpringMVC
    • SpringBoot
    • SpringCloud

2.3 Springのコア概念

2.3.1 現在のプロジェムの問題点

  • 問題点: 従来のJavaWeb開発に従うと、結合度が高くなります
    • 例: DAO層で内容が変更され、新しい関数を使用してデータ処理を行う場合、Service層も変更する必要があり、再コンパイルが必要です
  • ソリューション
    • オブジェクトを使用する際、newでオブジェクトを生成するのを避け、外部からオブジェクトを提供するように変更します

2.3.2 IOC、IOCコンテナ、Bean、DI —— **十分な結合の解除**

  • IOC(Inversion of Control)制御の反転: オブジェクトを使用する際、newでオブジェクトを生成するのを、外部からオブジェクトを提供するように変更することで、オブジェクトの作成制御権がプログラムから外部に移転されます。この思想(オブジェクトの作成制御権の移転)を制御の反転と呼びます。
  • IoCコンテナ: Springはコンテナを提供しており、これをIOCコンテナと呼び、IOC思想における「外部」の役割を果たします
  • Bean: IOCコンテナはオブジェクトの作成、初期化の一連の作業を担当します(データ層とビジネス層のクラスオブジェクトを含む)、作成または管理されるオブジェクトはIOCコンテナ内ではBeanオブジェクトと呼ばれます
  • DI(Dependency Injection)依存性の注入: IoCコンテナはBeanオブジェクト間の依存関係を解決するために、自動的にbeanオブジェクトのバインディングを行うプロセスです
    • 例: ServiceがDaoに依存する場合、IoCコンテナは2つのbeanオブジェクトをバインドします

3. 入門ケース

3.1 IOC入門ケース

3.1.1 入門ケースの思路分析

3.1.2 入門ケースのコード実装

  1. Mavenプロジェクトの作成
  2. Spring依存jarパッケージの追加
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  3. 必要なクラスの追加
    • Dao
      • インターフェース
        package com.example.repository;
        
        public interface ItemRepository {
            public void store();
        }
        
      • 実装クラス
        package com.example.repository.impl;
        
        import com.example.repository.ItemRepository;
        
        public class ItemRepositoryImpl implements ItemRepository {
            public void store() {
                System.out.println("item repository store ...");
            }
        }
        
    • Service
      • インターフェース
        package com.example.service;
        
        public interface ItemService {
            public void store();
        }
        
      • 実装クラス
        package com.example.service.impl;
        
        import com.example.repository.ItemRepository;
        import com.example.repository.impl.ItemRepositoryImpl;
        import com.example.service.ItemService;
        
        public class ItemServiceImpl implements ItemService {
            private ItemRepository itemRepository = new ItemRepositoryImpl();
        
            public void store() {
                System.out.println("item service store ...");
                itemRepository.store();
            }
        }
        
    • Main
      package com.example;
      
      import com.example.service.ItemService;
      import com.example.service.impl.ItemServiceImpl;
      
      public class Application {
          public static void main(String[] args) {
              ItemService itemService = new ItemServiceImpl();
              itemService.store();
              /**
               * コンソール出力:
               * item service store ...
               * item repository store ...
               */
          }
      }
      
  4. `resources`ディレクトリにSpring設定ファイルを追加: `applicationContext.xml`、設定ファイル内でbeanの設定を完了
    <?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の設定 -->
        <!--
            id : Beanに名前を付ける
            class : Beanのタイプを定義する
        -->
        <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
        <bean id="itemService" class="com.example.service.impl.ItemServiceImpl"/>
    
    </beans>
    
  5. IOCコンテナを取得し、コンテナからオブジェクトを取得してメソッドを呼び出す
    package com.example;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import com.example.service.ItemService;
    
    public class Application2 {
        public static void main(String[] args) {
            // IOCコンテナを取得
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            // Beanを取得
            ItemService itemService = (ItemService) context.getBean("itemService");
            itemService.store();
            /**
             * コンソール出力:
             * item service store ...
             * item repository store ...
             */
        }
    }
    

3.2 DI ( 依存性の注入 )入門ケース

IoC入門ケースでは、Service層は依然としてnewを使用してオブジェクトを作成しており、結合度は依然として高いです。

3.2.1 入門ケースの思路分析

3.2.2 入門ケースのコード実装

  • 依存されるクラスのメンバープロパティでオブジェクトを作成する部分を変更し、setメソッドを追加
    package com.example.service.impl;
    
    import com.example.repository.ItemRepository;
    import com.example.service.ItemService;
    
    public class ItemServiceImpl implements ItemService {
        // ビジネス層でnewを使用して作成されたrepositoryオブジェクトを削除
        private ItemRepository itemRepository;
    
        public void store() {
            System.out.println("item service store ...");
            itemRepository.store();
        }
        // 作成するメンバーオブジェクトに対応するsetメソッドを提供
        public void setItemRepository(ItemRepository itemRepository) {
            this.itemRepository = itemRepository;
        }
    }
    
  • 設定ファイルを変更し、依存されるクラスとの関係を設定
    <?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の設定 -->
        <!--
            id : Beanに名前を付ける
            class : Beanのタイプを定義する
        -->
        <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
        <bean id="itemService" class="com.example.service.impl.ItemServiceImpl">
            <!-- ServiceとRepositoryの関係を設定 -->
            <!--
                propertyタグ : 現在のBeanのプロパティを設定する
                name属性 : どの具体的なプロパティ名を設定するかを指定、setメソッドを実装する必要がある
                ref属性 : どのBeanを参照(参照)するかを指定
            -->
            <property name="itemRepository" ref="itemRepository"/>
        </bean>
    
    </beans>
    

4. IOC関連内容

4.1 Beanの基本設定

4.1.1 beanの基本設定( id と class )

カテゴリ 説明
名称 bean
タイプ タグ
所属 beansタグ
機能 Springコアコンテナが管理するオブジェクトを定義する
形式 <beans> <bean/> <bean></bean> <beans>
属性リスト id: beanのid、コンテナを通じてid値で対応するbeanを取得でき、コンテナ内でidは一意
class: beanのタイプ、つまり設定されたbeanの完全パス名(インターフェースはインスタンス化できない)
<bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
<bean id="itemService" class="com.example.service.impl.ItemServiceImpl"></bean>

4.1.2 beanの別名name属性

  1. 別名の設定
    • beanタグのname属性を使用してbeanに別名を設定
    • 1つのbeanに複数の別名を設定でき、カンマまたはスペースで区切る
    • beanのref属性は対応するbeanのname属性値を参照できますが、idを参照することを推奨し、一貫性を保ちます
      <!--
          id : Beanに名前を付ける
          class : Beanのタイプを定義する
      -->
      <bean id="itemRepository" name="repo" class="com.example.repository.impl.ItemRepositoryImpl"/>
      <bean id="itemService" name="service,service2 itemEbi" class="com.example.service.impl.ItemServiceImpl">
          <!-- ServiceとRepositoryの関係を設定 -->
          <!--
              propertyタグ : 現在のBeanのプロパティを設定する
              name属性 : どの具体的なプロパティ名を設定するかを指定、setメソッドを実装する必要がある
              ref属性 : どのBeanを参照(参照)するかを指定
          -->
          <property name="itemRepository" ref="repo"/>
      </bean>
      
  2. 名称からコンテナ内のbeanオブジェクトを取得
    • idからコンテナ内のbeanオブジェクトを取得する方法と同じ

4.1.3 beanの作用範囲scope設定

  • Beanオブジェクトを複数回作成するデフォルトはシングルトンであり、scope属性を使用してシングルトン以外に設定できます
    • 核心設定ファイル
      <!-- itemRepositoryにscope="prototype"属性が設定されています -->
      <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl" scope="prototype"/>
      
      <bean id="itemService" name="service,service2 itemEbi" class="com.example.service.impl.ItemServiceImpl">
          <property name="itemRepository" ref="itemRepository"/>
      </bean>
      
    • 実行
      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      // シングルトン
      ItemService itemService1 = (ItemService) context.getBean("service");
      ItemService itemService2 = (ItemService) context.getBean("service");
      System.out.println(itemService1);   // com.example.service.impl.ItemServiceImpl@25bbe1b6
      System.out.println(itemService2);   // com.example.service.impl.ItemServiceImpl@25bbe1b6
      // 非シングルトン
      ItemRepository itemRepository1 = (ItemRepository) context.getBean("itemRepository");
      ItemRepository itemRepository2 = (ItemRepository) context.getBean("itemRepository");
      System.out.println(itemRepository1);   // com.example.repository.impl.ItemRepositoryImpl@5702b3b1
      System.out.println(itemRepository2);   // com.example.repository.impl.ItemRepositoryImpl@69ea3742
      
  • 整理
    カテゴリ 説明
    名称 scope
    タイプ 属性
    所属 beanタグ
    機能 beanの作用範囲を定義、デフォルトはシングルトン(デフォルトはシングルトンでリソースを節約)、設定可能
    * singleton: シングルトン
    * prototype: 非シングルトン
    <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl" scope="prototype"/>
  • 拡張
    • コンテナに管理させるのに適したbean
      • Dao層オブジェクト
      • Service層オブジェクト
      • Controller層オブジェクト
      • Utilsオブジェクト
    • コンテナに管理させるのに適さないbean
      • 実体をカプセル化するドメインオブジェクト、それぞれが異なるデータを持つ

4.2 beanのインスタンス化

beanの本質はオブジェクトであり、beanの作成は引数なしのコンストラクタで完了します(内部では暴力的なリフレクションが使用されます)

beanの3つのインスタンス化方法:

  • コンストラクタ
  • 静的ファクトリ
  • インスタンスファクトリ

4.2.1 環境準備

4.2.2 コンストラクタインスタンス化

  1. 作成されるクラスの準備
    • Repository
      public class ItemRepositoryImpl implements ItemRepository {
      
          // 検証: beanのインスタンス化の本質は引数なしコンストラクタの呼び出しであり、手動で実装してコンソールに出力内容を表示
          // 検証: 内部では暴力的なリフレクションメカニズムが使用され、コンストラクタがprivateの場合でもbeanのインスタンス化に影響しない
          private ItemRepositoryImpl() {
              System.out.println("item repository constructor is running ....");
          }
      
          public void store() {
              System.out.println("item repository store ...");
          }
      }
      
  2. クラスをSpringコンテナに設定
    <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
    
  3. 実行プログラムの記述
    public class AppForInstanceItem {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            ItemRepository itemRepository = (ItemRepository) ctx.getBean("itemRepository");
    
            itemRepository.store();
            
            /**
             * コンソール出力:
             * item repository constructor is running ....
             * item repository store ...
             *
             * Process finished with exit code 0
             */
        }
    }
    

4.2.3 静的ファクトリインスタンス化

4.2.3.1 ファクトリ方式でbeanを作成

ファクトリ方式を使用してオブジェクトを作成することで、ある程度の結合を解除できますが、ファクトリをSpringに管理させるより(4.2.3.2参照)効果的ではありません。

  • Repository
    public class OrderRepositoryImpl implements OrderRepository {
        public void store() {
            System.out.println("order repository store ...");
        }
    }
    
  • Factory
    // 静的ファクトリでオブジェクトを作成
    public class OrderRepositoryFactory {
        public static OrderRepository getOrderRepository(){
            System.out.println("factory setup....");
            return new OrderRepositoryImpl();
        }
    }
    
  • Main
    public class AppForInstanceOrder {
        public static void main(String[] args) {
            // 静的ファクトリでオブジェクトを作成
            OrderRepository orderRepository = OrderRepositoryFactory.getOrderRepository();
            orderRepository.store();
            /**
             * コンソール出力:
             * factory setup....
             * order repository store ...
             */
        }
    }
    
4.2.3.2 静的ファクトリインスタンス化

ファクトリをSpringに管理させ、beanをインスタンス化します。

  • 核心設定ファイル
    <!-- propertyタグ : 現在のBeanのプロパティを設定する -->
    <!-- class属性 : 使用するファクトリの完全クラス名 -->
    <!-- factory-method属性 : ファクトリ内でbeanを作成するメソッド名 -->
    <bean id="orderRepository" class="com.example.factory.OrderRepositoryFactory" factory-method="getOrderRepository"/>
    
  • Main
    public class AppForInstanceOrder {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            OrderRepository orderRepository = (OrderRepository) ctx.getBean("orderRepository");
    
            orderRepository.store();
            /**
             * コンソール出力:
             * factory setup....
             * order repository store ...
             */
        }
    }
    

4.2.4 インスタンスファクトリ(非静的)とFactoryBean

4.2.4.1 環境準備
  • Repository
    public class UserRepositoryImpl implements UserRepository {
        public void store() {
            System.out.println("user repository store ...");
        }
    }
    
  • Factory
    // インスタンスファクトリでオブジェクトを作成
    public class UserRepositoryFactory {
        public UserRepository getUserRepository(){
            return new UserRepositoryImpl();
        }
    }
    
4.2.4.2 インスタンスファクトリインスタンス化
  • 核心設定ファイル
    <!-- ファクトリ内のRepository取得メソッドは非静的であるため、ファクトリオブジェクトを取得する必要があります -->
    <bean id="userFactory" class="com.example.factory.UserRepositoryFactory"/>
    
    <!-- ファクトリを指定し、ファクトリの対応するメソッドを使用して必要なbeanオブジェクトを取得 -->
    <!-- factory-bean: ファクトリbeanを指定 -->
    <bean id="userRepository" factory-bean="userFactory" factory-method="getUserRepository"/>
    
  • Main
    public class AppForInstanceUser {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            UserRepository userRepository = (UserRepository) ctx.getBean("userRepository");
            
            userRepository.store();
            /**
             * コンソール出力:
             * user repository save ...
             */ 
        }
    }
    
4.2.4.3 FactoryBeanの使用(改良:インスタンスファクトリインスタンス化)

**4.2.4.2 インスタンスファクトリインスタンス化**には問題があるため、改良が必要です:

  1. ファクトリの`bean`オブジェクトは単に使用するためのもので、意味がありません
  2. 各必要な`bean`に対して、`factory-method`属性値が固定されておらず、毎回設定が必要です

`FactoryBean`を使用して改良します。

  • FactoryBeanファクトリクラス
    // FactoryBeanでオブジェクトを作成するには、FactoryBean<T>インターフェースを実装し、ジェネリクスを指定する必要があります
    public class UserRepositoryFactoryBean implements FactoryBean<UserRepository> {
    
        // 元のインスタンスファクトリ内のオブジェクト作成メソッドを代替
        public UserRepository getObject() throws Exception {
            return new UserRepositoryImpl();
        }
    
        // Objectのタイプを指定し、対応するクラスのバイトコードファイルを返す
        public Class getObjectType() {
            return UserRepository.class;
        }
        
        // 作成されたbeanがシングルトンかどうかを返す、trueを返すとシングルトン、このメソッドをオーバーライドしない場合はデフォルトでシングルトン
        public boolean isSingleton() {
            return true;
        }
    }
    
  • 核心設定ファイル
    <!-- <bean id="userFactory" class="com.example.factory.UserRepositoryFactory"/> -->
    <!-- <bean id="userRepository" factory-bean="userFactory" factory-method="getUserRepository"/> -->
    <bean id="userRepository" class="com.example.factory.UserRepositoryFactoryBean"/>
    
  • Main
    • 4.2.4.2と同じで変更不要

4.3 beanのライフサイクル

beanのライフサイクルには以下のいくつかの段階があります:

  • コンテナの初期化
    1. オブジェクトの作成(メモリの割り当て)
    2. コンストラクタの実行
    3. プロパティ注入(set操作)の実行
    4. beanの初期化メソッドの実行
  • beanの使用
    1. ビジネス操作の実行
  • コンテナのクローズ/破棄
    1. beanの破棄メソッドの実行

4.3.1 環境準備

  • Repository
    public class ItemRepositoryImpl implements ItemRepository {
        public void store() {
            System.out.println("item repository store ...");
        }
    }
    
  • 核心設定ファイル
    <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
    
  • Main
    public class AppForLifeCycle {
        public static void main( String[] args ) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            ItemRepository itemRepository = (ItemRepository) ctx.getBean("itemRepository");
            itemRepository.store();
            /**
             * item repository store ...
             */
        }
    }
    

4.3.2 ライフサイクル設定

  1. クラスに初期化と破棄メソッドを追加
    public class ItemRepositoryImpl implements ItemRepository {
        public void store() {
            System.out.println("item repository store ...");
        }
        // beanの初期化に対応する操作を示す
        public void init(){
            System.out.println("init...");
        }
        // beanの破棄前に対応する操作を示す
        public void destroy(){
            System.out.println("destroy...");
        }
    }
    
  2. 核心設定ファイルで設定
    <!--init-method: bean初期化ライフサイクルコールバック関数を設定-->
    <!--destroy-method: bean破棄ライフサイクルコールバック関数を設定、シングルトンオブジェクトにのみ適用-->
    <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl" init-method="init" destroy-method="destroy"/>
    
  3. プログラムを実行し、コンソール出力にはinitのみが含まれる
    • 上記の実行状況に示されているように、コンソールに`destroy...`が出力されていないため、破棄メソッドが実行されていません。この問題は4.3.3と4.3.4で解決します
    /**
     * init...
     * item repository store ...
     */
    

4.3.3 closeでコンテナをクローズ

  • 破棄メソッドが実行されない原因とソリューションの分析
    • 原因: プログラムの実行が終了すると、JVM仮想機が直接クローズされ、破棄メソッドが実行される前に終了してしまいます
    • ソリューション: JVM仮想機をクローズする前に手動でコンテナをクローズし、破棄メソッドを実行できます
  • 実装
    • 問題点: `ApplicationContext`には`close()`が実装されていません
      • ソリューション: その実装クラス`ClassPathXmlApplicationContext`が実装しています
    public class AppForLifeCycle {
        public static void main( String[] args ) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            ItemRepository itemRepository = (ItemRepository) ctx.getBean("itemRepository");
            itemRepository.store();
            //コンテナをクローズ
            ctx.close();
            /**
             * init...
             * item repository store ...
             * destroy...
             */
        }
    }
    

4.3.4 フック登録でコンテナをクローズ

  • この方式を使用する理由
    • closeでコンテナをクローズは比較的暴力的であり、closeの後に実行されていない文があると問題が発生する可能性があります
    • フック登録でコンテナをクローズする文はどこに書くこともでき、JVM仮想機のクローズ前のみで破棄を実行します
  • フック登録でコンテナをクローズする使用方法
    • Main内
      //シャットダウンフック関数を登録し、仮想機が退出する前にこの関数をコールバックしてコンテナをクローズ
      ctx.registerShutdownHook();
      

4.3.5 ライフサイクル管理のインターフェース方式

Springはライフサイクル制御のためにインターフェースを提供しており、上記のコードに以下のコードを追加してデモンストレーションします。

  • 核心設定ファイル(init-methodとdestroy-method属性は不要)
    <bean id="itemService" class="com.example.service.impl.ItemServiceImpl">
        <property name="itemRepository" ref="itemRepository"/>
    </bean>
    
  • Service
    public class ItemServiceImpl implements ItemService, InitializingBean, DisposableBean {
        private ItemRepository itemRepository;
    
        public void setItemRepository(ItemRepository itemRepository) {
            System.out.println("set ....."); // 日本語に変更
            this.itemRepository = itemRepository;
        }
    
        public void store() {
            System.out.println("item service store ...");
            itemRepository.store();
        }
    
        public void destroy() throws Exception {
            System.out.println("service destroy");
        }
    
        // メソッド名の通り、このメソッドはsetメソッドの後に実行される
        public void afterPropertiesSet() throws Exception {
            System.out.println("service init");
        }
    }
    
  • Main
    public class AppForLifeCycle {
        public static void main( String[] args ) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            ItemRepository itemRepository = (ItemRepository) ctx.getBean("itemRepository");
            itemRepository.store();
            //シャットダウンフック関数を登録し、仮想機が退出する前にこの関数をコールバックしてコンテナをクローズ
            ctx.registerShutdownHook();
            /**
             * init...
             * set .....
             * service init
             * item repository store ...
             * service destroy
             * destroy...
             */
        }
    }
    

5、DI(依存性の注入)関連内容

- クラスにデータを渡す方法

  • 通常のメソッド(setter)
  • コンストラクタ(コンストラクタ)

- コンテナ内のbeanの可能性のあるデータタイプ

  • 参照型
  • 単純型(基本データ型とString)

5.1 setter注入(推奨、使用簡単)

5.1.1 参照型データの注入

  1. クラスにメンバ変数を与え、そのsetterを実装
    public class ItemServiceImpl implements ItemService {
        private ItemRepository itemRepository;
        private UserRepository userRepository;
        //setter注入では注入するオブジェクトのsetメソッドを提供する必要がある
        public void setUserRepository(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
        //setter注入では注入するオブジェクトのsetメソッドを提供する必要がある
        public void setItemRepository(ItemRepository itemRepository) {
            this.itemRepository = itemRepository;
        }
    
        public void store() {
            System.out.println("item service store ...");
            itemRepository.store();
            userRepository.store();
        }
    }
    
  2. 核心設定ファイル
    <!-- 注入されるものは設定し、beanとして定義 -->
    <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
    <bean id="userRepository" class="com.example.repository.impl.UserRepositoryImpl"/>
    
    <!-- 参照型を注入 -->
    <bean id="itemService" class="com.example.service.impl.ItemServiceImpl">
        <!--propertyタグ:注入プロパティを設定-->
        <!--name属性:注入するプロパティ名を設定、実際はsetメソッドに対応する名前-->
        <!--ref属性:注入する参照型beanのidまたはnameを設定-->
        <property name="itemRepository" ref="itemRepository"/>
        <property name="userRepository" ref="userRepository"/>
    </bean>
    

5.1.2 単純型データの注入

  1. クラスでメンバ変数を定義し、そのsetterを実装
    public class ItemRepositoryImpl implements ItemRepository {
        private String databaseName;
        private int connectionCount;
        //setter注入では注入するオブジェクトのsetメソッドを提供する必要がある
        public void setConnectionCount(int connectionCount) {
            this.connectionCount = connectionCount;
        }
        //setter注入では注入するオブジェクトのsetメソッドを提供する必要がある
        public void setDatabaseName(String databaseName) {
            this.databaseName = databaseName;
        }
    
        public void store() {
            System.out.println("item repository store ..."+databaseName+","+connectionCount);
        }
    }
    
  2. 核心設定ファイル
    <!-- 単純型を注入 -->
    <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl">
        <!--propertyタグ:注入プロパティを設定-->
        <!--name属性:注入するプロパティ名を設定、実際はsetメソッドに対応する名前-->
        <!--value属性:注入する単純型データ値を設定、Spring内部で自動的に型変換を行う-->
        <property name="connectionCount" value="100"/>
        <property name="databaseName" value="mysql"/>
    </bean>
    

5.2 コンストラクタ注入

5.2.1 コンストラクタで参照型データを注入

  1. クラスにメンバ変数を与え、注入が必要なメンバを初期化するために引数付きコンストラクタを使用
    public class ItemServiceImpl implements ItemService {
        private ItemRepository itemRepository;
        private UserRepository userRepository;
    
        public ItemServiceImpl(ItemRepository itemRepository, UserRepository userRepository) {
            this.itemRepository = itemRepository;
            this.userRepository = userRepository;
        }
    
        public void store() {
            System.out.println("item service store ...");
            itemRepository.store();
            userRepository.store();
        }
    }
    
  2. 核心設定ファイル
    <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
    <bean id="userRepository" class="com.example.repository.impl.UserRepositoryImpl"/>
    
    <bean id="itemService" class="com.example.service.impl.ItemServiceImpl">
        <!-- このref属性値は:コンストラクタのパラメータ名 -->
        <constructor-arg name="userRepository" ref="userRepository"/>
        <constructor-arg name="itemRepository" ref="itemRepository"/>
    </bean>
    

5.2.2 コンストラクタで単純型データを注入

  1. クラスにメンバ変数を与え、注入が必要なメンバを初期化するために引数付きコンストラクタを使用
    public class ItemRepositoryImpl implements ItemRepository {
        private String databaseName;
        private int connectionCount;
    
        public ItemRepositoryImpl(String databaseName, int connectionCount) {
            this.databaseName = databaseName;
            this.connectionCount = connectionCount;
        }
    
        public void store() {
            System.out.println("item repository store ..."+databaseName+","+connectionCount);
        }
    }
    
  2. 核心設定ファイル
    <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl">
        <!-- コンストラクタパラメータ名に基づいて注入 -->
        <constructor-arg name="connectionCount" value="10"/>
        <constructor-arg name="databaseName" value="mysql"/>
    </bean>
    

5.2.3 【補足】異なるシナリオでのコンストラクタ注入単純型データのその他の書き方 —— 結合解除のため

<!-- 1. 標準的な記述 -->
<bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl">
    <constructor-arg name="connectionCount" value="10"/>
    <constructor-arg name="databaseName" value="mysql"/>
</bean>

<!-- 2. パラメータ名の問題を解決し、パラメータ名と結合しない -->
<bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl">
    <constructor-arg type="int" value="10"/>
    <constructor-arg type="java.lang.String" value="mysql"/>
</bean>

<!-- 3. パラメータタイプの重複問題を解決し、位置を使用してパラメータマッチングを解決 -->
<bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl">
    <constructor-arg index="0" value="mysql"/>
    <constructor-arg index="1" value="100"/>
</bean>

5.3 自動設定

- IoCコンテナがbeanが依存するリソースに基づいてコンテナ内で自動的に検索し、beanに注入するプロセスを自動設定と呼びます

- 自動設定の方法:

  • タイプによる(推奨)
  • 名前による(結合度高)
  • コンストラクタによる(推奨しない)
  • 自動設定を無効にする

- 自動設定の特徴

  • 自動設定は参照型依存性注入に使用され、単純型を操作できません
  • タイプによる設定(byType)を使用する場合、コンテナ内で同じタイプ(class)のbeanが一意であることを保証する必要があります。推奨されます
  • 名前による設定(byName)を使用する場合、コンテナ内に指定された名前(id)のbeanが存在する必要があります。変数名と設定が結合するため、推奨されません
  • 自動設定の優先順位はsetter注入とコンストラクタ注入より低く、同時に表示される場合、自動設定設定は無効になります
  • タイプによる(推奨)
    • 注意事項
      1. 注入されるbeanのクラスはsetterを実装する必要があります
      2. 注入されるbean(完全クラス名、つまりclass属性)は一意である必要があります
      3. タイプによる注入時、注入されるbeanは`id`を持つ必要はありません
    • 設定
      <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
      <bean id="itemService" class="com.example.service.impl.ItemServiceImpl" autowire="byType"/>
      
  • 名前による
    • 注意事項
      1. 注入されるbeanのクラスはsetterを実装する必要があります
      2. 注入されるbeanのid属性はクラス内のメンバ名、正確にはsetter名と一致する必要があります。そうでないと空のオブジェクトが与えられ、NullPointerExceptionが発生します
      3. 変数名と設定の結合
    • 設定コード
      <bean id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl"/>
      <bean id="itemService" class="com.example.service.impl.ItemServiceImpl" autowire="byName"/>
      

5.4 コレクション注入

- ListとArrayは相互に使用できます

- コレクションの要素が参照型の場合、``を使用しません

<ref bean="beanのid"/>
  • Repository
    public class ItemRepositoryImpl implements ItemRepository {
        private int[] array;
        private List<String> list;
        private Set<String> set;
        private Map<String,String> map;
        private Properties properties;
    
        public void setArray(int[] array) {
            this.array = array;
        }
    
        public void setList(List<String> list) {
            this.list = list;
        }
    
        public void setSet(Set<String> set) {
            this.set = set;
        }
    
        public void setMap(Map<String, String> map) {
            this.map = map;
        }
    
        public void setProperties(Properties properties) {
            this.properties = properties;
        }
    
        public void store() {
            System.out.println("item repository store ...");
    
            System.out.println("配列を反復処理:" + Arrays.toString(array));
    
            System.out.println("Listを反復処理" + list);
    
            System.out.println("Setを反復処理" + set);
    
            System.out.println("Mapを反復処理" + map);
    
            System.out.println("Propertiesを反復処理" + properties);
        }
    }
    
  • 核心設定ファイル
    <?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 id="itemRepository" class="com.example.repository.impl.ItemRepositoryImpl">
            <!-- 配列注入 -->
            <property name="array">
                <array>
                    <value>100</value>
                    <value>200</value>
                    <value>300</value>
                </array>
            </property>
            <!-- リストコレクション注入 -->
            <property name="list">
                <list>
                    <value>item1</value>
                    <value>item2</value>
                    <value>item3</value>
                    <value>item4</value>
                </list>
            </property>
            <!-- Setコレクション注入 -->
            <property name="set">
                <set>
                    <value>item1</value>
                    <value>item2</value>
                    <value>item3</value>
                    <value>item3</value>
                </set>
            </property>
            <!-- Mapコレクション注入 -->
            <property name="map">
                <map>
                    <entry key="country" value="china"/>
                    <entry key="province" value="henan"/>
                    <entry key="city" value="kaifeng"/>
                </map>
            </property>
            <!-- Properties注入 -->
            <property name="properties">
                <props>
                    <prop key="country">china</prop>
                    <prop key="province">henan</prop>
                    <prop key="city">kaifeng</prop>
                </props>
            </property>
        </bean>
    </beans>
    
  • Main
    public class AppForDIcollection {
        public static void main( String[] args ) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            ItemRepository itemRepository = (ItemRepository) ctx.getBean("itemRepository");
    
            itemRepository.store();
        }
        /**
         * item repository store ...
         * 配列を反復処理:[100, 200, 300]
         * Listを反復処理[item1, item2, item3, item4]
         * Setを反復処理[item1, item2, item3]
         * Mapを反復処理{country=china, province=henan, city=kaifeng}
         * Propertiesを反復処理{province=henan, city=kaifeng, country=china}
         */
    }
    

タグ: Spring IoC DI Bean コンテナ

5月27日 12:12 投稿