taglibでAutowiredしたかった

2015年1月29日
本件に関して本文を修正しました。

以前の内容はそのまま下部に残してありますが、
役に立ちませんのであしからず。

### ざっくり言うと
---
Spring管理のクラスをtaglibでインジェクションしたい時は`RequestContextAwareTag`をextendsしようね！って話



### 前提
---
1. `struts`と`spring`が混在するプロジェクト
2. `applicationContext.xml`を2つ有する
3. 一つは`struts-config.xml`を介するコンテキスト
4. もう一つは`struts-config.xml`に寄らないコンテキスト
5. 以下では`3`を`applicationContext.xml`、`4`を`applicationContextForSpring.xml`として扱う
6. 新規でサービスクラス(HogeService.java)を作成し、`applicationContextForSpring.xml`に記載した


### 目的
---
`taglib`で`HogeService`クラスをインジェクションすること

### 試行1
---
手段：おとなしく`@Autowired`を書いてみる
結果：インジェクション出来ず`NullPointerException`が発生`[\[1\]](http://stackoverflow.com/questions/8243847/spring-and-auto-wiring-nullpointerexception)

### 試行2
---
手段：`taglib`で`TagSupport`ではなく`RequestContextAwareTag`をextendsし、以下のようにして取得

```java
public class HogeTag extends RequestContextAwareTag {
    HogeService hogeService = getRequestContext().getWebApplicationContext().getBean(HogeService.class);
    …
}
```
結果：取得できず`NullPointerException`


### 手がかり
---
1. `getBeanDefinitionNames()`でbean一覧を出力してみると`applicationContext.xml`管理のものが取得できている様子
2. `taglib`の呼び出し元クラスは`applicationContext.xml`で管理されている

### 予想
---
* taglibを呼び出すviewが、ActionとControllerのどちらに属してるかによって取得できるコンテキストが変わるのではないか
* `getRequestContext().getWebApplicationContext()`で取得できるbeanが
`Action`の場合`applicationContext.xml`、
`Controller`の場合は`applicationContextForSpring.xml`
で管理しているものではないか

### 検証
---
* ControllerとActionそれぞれで呼び出し元クラスを準備
* 以下のテストtaglibを作成。

```java
public class TestTag extends RequestContextAwareTag {
	@Override
	protected int doStartTagInternal() throws Exception {
		Integer num = this.getRequestContext()
          .getWebApplicationContext()
          .getBean("hogeService", Integer.class);
		System.out.println(num);
		return SKIP_BODY;
	}
}
```
* テンプレートから呼び出してみる

結果：
   `Controller`：hogeServiceがとれた
   `Action`：そんなbeanねーよ！ってなった
予想は合っている様子。

### 解決策
---
1. `HogeService`を`applicationContext.xml`で管理する
2. `taglib`の呼び出し元クラスを`applicationContextForSpring.xml`で管理する
 
今回の場合、呼び出し元は既存のActionクラスだったので`1`で解決しました。※ @making@githubさんにコメントでアドバイスいただいたとおりです。

### まとめ
---
呼び出すbeanがどのコンテキストで管理されているかが問題なのではなく、taglib呼び出し元がAction・Controllerどちらなのかによって取得できるbean一覧が異なる。

```
Action → applicationContext.xml管理のbean
Controller → applicationContextForSpring.xml管理のbean
```

### 疑問
---
Action・Controller両方でtaglib呼び出したい！ってなったら両方のコンテキストで管理しないといけないのだろうか…。







--------------------------------- 以下、修正前の本文です ---------------------------------
------ 読めば何を勘違していたかわかると思いますが役には立ちません ------
###やりたかったこと
---
カスタムタグでサービスクラスをインジェクションしたかった。



###できなかった
---
とりあえずこんな感じで。

```java
public class HogeTestTag extends TagSupport {
    @Autowired
    private HogeService hogeService;
    …
}
```
TagSupportをextendsしてるクラスで黙って`@Autowired`した。
出来なかった…。
**( ´∀｀)＜ぬるぽ**って怒られてしまった…。

どうやら`@Autowired`ではダメらしい。[\[1\]](http://stackoverflow.com/questions/8243847/spring-and-auto-wiring-nullpointerexception)

 
 

**どうしたらいいんだぁぁぁぁー！！！ヽ(`Д´)ﾉｳﾜｧｧｧﾝ!!**



###できるよん
---
結論、先の注釈に書いてあったんだがｗ

RequestContextAwareTagをextendsして`getRequestContext().getWebApplicationContext()`でWebApplicationContextを取得して頑張ればいい。

つまりこういうこと。

```java
public class HogeTag extends RequestContextAwareTag {
    HogeService hogeService = getRequestContext().getWebApplicationContext().getBean(HogeService.class);
    …
}
```
###あっさり解決
---
かと思われたのだが。。。
上記のやり方で取得できるbeanは`web.xml`でこんな感じで書かれてるやつの`applicationContext.xml`で`context:component-scan`的なことされてるやつだけだったのです。

```xml
<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
```
今回インジェクションしたかったのは`web.xml`でservletとして大元のapplicationContext.xmlとは別に読み込まれているcontextだったわけなのです。
~~なんでapplicationContext的なのが2つある作りなのか、その意味を知る者はいない…~~

おかげで`getBean(HogeService.class)`が0件という残念な結果に。。。
`getBeanDefinitionCount()`でbeanの件数を確認してみたり、`getBeanDefinitionNames()`で登録されてるbean一覧を出してみたり、そんなこんなで上記のやつらが読み込まれているとわかったわけなのです。


**わかったけどこれじゃダメなんやー！(つд⊂)ｴｰﾝ**





###解決編
---
```java
HogeService hogeService = new ClassPathXmlApplicationContext("applicationContext.xml").getBean(HogeService.class);
```
設定ファイルを指定して`ClassPathXmlApplicationContext`取ってやりませう。**完**

但し、これtaglib呼び出し毎にやると毎回スキャンされてしまい、更にはログが恐ろしいことになるので、staticで初回だけ読み込むとかなんとか工夫しよう。(一度失敗した身ｗ)

そもそもcontextが1つならこんなに悩んだり変なことする必要は無いんだが。。。
先人の考えはわからんのでとりあえずこんな感じの解決で。