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 | JSONやXML等を返す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" /> …