【Spring】DI

DIとは

インスタンスを共有化して効率を上げる仕組み。→クラスを使うときにnewしない
また、メンバ変数への代入をクラス内で行うのではなく、外部(xmlファイル等)から設定するという考え方。

DIの注意点

@Controllerを付与したControllerクラスや@Serviceを付与したServiceクラスもDI対象でシングルトン。
シングルトンはアプリケーションで1つのインスタンスを共有するため、メンバ変数が共有されてしまう。
※ローカル変数は対象外

@Controller
public class OfferController {
    // メンバ変数
    String sampleName;

    @GetMapping("/offer_initDisplay")
    public String initDisplay(Model model){
        System.out.println(sampleName);    // 初期表示時はnullだが以降は「a」が表示
        // メンバ変数に代入
        serviceName = "a";
        return "offer";
    }
}

メンバ変数の共有化を防ぐ方法は
1.メンバ変数を使用しない
2.@Scopeアノテーションを使用する(requestまたはprototype)
※Controllerの場合request、Serviceの場合prototypeで良い

@Controller
@Scope("request")
public class OfferController {
    // メンバ変数
    String sampleName;

    @GetMapping("/offer_initDisplay")
    public String initDisplay(Model model){
        System.out.println(sampleName);    // リクエスト毎にインスタンスが作成されるためnull(共有されない)
        // メンバ変数に代入
        serviceName = "a";
        return "offer";
    }
}

使用方法1.アノテーションを使用(Webアプリの場合)

アノテーションを付与し、コンポーネントスキャンすることでDIコンテナに登録する。
※Bean登録するクラスはコンストラクタの使用不可っぽい
※Bean登録するクラスがメンバ変数を使用している場合、@Scope("prototype")でメンバ変数が共有されることを防ぐこと。DIはシングルトンのためメンバ変数が共有されることを覚えておく。

Beanの登録

コンポーネンストキャンの対象にする
servlet-context.xml(spring-mvc.xml)

<beans>
  ・・・
  <!-- このパスの配下がコンポーネントスキャンの対象となる -->
  <context:component-scan base-package="jp.co.demo" />
  ・・・
</beans>

コンポーネントスキャン対象のアノテーション
コンポーネントスキャン対象かつ下記アノテーションがついている場合、自動でBean登録される。

@Controller このアノテーションを付与したコンポーネントでは、クライアントからのリクエスト/レスポンスに関わる処理をする。
@Service ビジネスロジックを実装するコンポーネントであることを表すアノテーション
@Repository データの永続化に関わる処理を提供するコンポーネント。ORMなどを利用して、データのCRUD処理を実装する。
@Component 上記3つに当てはまらないコンポーネント。ユーティリティクラスなどに付与する。
@Configuration クラス宣言の前に記述します。このアノテーションは、このクラスがBeanの設定を行うものであることを示します。Bean設定クラスには常にこれをつけます。
@RestController JSONXML等を返すWebAPI用のコントローラに付与する。
DIの方法1.(コンストラクタインジェクション)

一番オススメされる方法。final化できるのが特徴。

@Controller
@Scope("request")
public class OfferController {

    // コンストラクタインジェクション
    private final OfferService offerService;
    private final BaseOfferService baseOfferService;
    // ※@Autowired省略可能
    @Autowired
    public OfferController(OfferService offerService, BaseOfferService baseOfferService) {
        this.offerService = offerService;
        this.baseOfferService = baseOfferService;
    }

    @GetMapping("/offer_initDisplay")
    public String initDisplay(Model model) {
        offerService.setServiceName("a");
        return "offer/offer";
    }
}
DIの方法2.(セッターインジェクション)

DIするクラスごとにセッターを増やす必要がある。

@Controller
@Scope("request")
public class OfferController {

    // セッターインジェクション
    private OfferService offerService;
    private BaseOfferService baseOfferService;

    @Autowired
    public void setOfferService(OfferService offerService) {
        this.offerService = offerService;
    }
    @Autowired
    public void setBaseOfferService(BaseOfferService baseOfferService) {
        this.baseOfferService = baseOfferService;
    }

    @GetMapping("/offer_initDisplay")
    public String initDisplay(Model model) {
        offerService.setServiceName("a");
        return "offer/offer";
    }
}
DIの方法3.(フィールドインジェクション)

一番簡単に書ける。

@Controller
@Scope("request")
public class OfferController {

    // フィールドインジェクション
    @Autowired
    private OfferService offerService;
    @Autowired
    private BaseOfferService baseOfferService;

    @GetMapping("/offer_initDisplay")
    public String initDisplay(Model model) {
        offerService.setServiceName("a");
        return "offer/offer";
    }
}

使用方法2.XMLを使用

Bean登録

XMLにDIコンテナに登録するBean(クラス)を記載する。

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の登録のみ -->
    <!-- ※初期値の設定を省略したメンバ変数はnullが設定される -->
    <bean id="baseOfferForm" class="jp.co.demo.common.form.BaseOfferForm" />

    <!-- Beanの登録+メンバ変数への初期値を設定(セッターインジェクション) -->
    <!-- ※初期値の設定を省略したメンバ変数はnullが設定される -->
    <bean id="offerForm" class="jp.co.demo.offer.form.OfferForm">
        <property name="name" value="nameの値" />
        <property name="baseOfferForm" ref="baseOfferForm" />    <!-- あらかじめ定義されたbeanIdを使用する場合、refを使用する -->
    </bean>

    <!-- Beanの登録+クラスでコンストラクタを使用してメンバ変数を設定する場合(コンストラクタインジェクション) -->
    <!-- ※typeは型、indexは引数の順番、refはbeanIdを使用する場合 -->
    <bean id="offerService" class="jp.co.demo.offer.service.OfferService">
        <constructor-arg value="引数1つ目の値" type="String" index="0" />
        <constructor-arg ref="offerForm" index="1" />
    </bean>
</beans>

【呼び出し方法1】

public class OfferSample {

    public String initDisplay() {
        ClassPathXmlApplicationContext contxt =
                new ClassPathXmlApplicationContext("beans/serviceBeans.xml");
        // xmlのbeanIDを指定する
        OfferService service = (OfferService)contxt.getBean("offerService");

        System.out.println(service.getServiceName());

        contxt.close();
        return "offer/offer";
    }
}

【呼び出し方法2】
ContextLoaderを使用する。
Beanのファイルをweb.xmlかコンテキストファイルに事前に紐づける方法。

public class OfferSample {

    public String initDisplay() {
        ApplicationContext contxt = ContextLoader.getCurrentWebApplicationContext();

        // xmlのbeanIDを指定する
        OfferService service = (OfferService)contxt.getBean("offerService");

        System.out.println(service.getServiceName());

        contxt.close();
        return "offer/offer";
    }
}

ContextLoaderで呼び出せるようにするにはweb.xmlかコンテキストファイルにBeanのxmlを追記する。
・web.xmlに記載する場合

<context-param>
    <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/root-context.xml
            classpath*:beans/serviceBeans.xml
        </param-value>
  </context-param>

・コンテキストファイルに記載する場合
root-context.xml(applicationContext.xml)

<import resource="classpath*:beans/serviceBeans.xml" />