今改めて考えてみるSpringとStrutsを連携させる方法

既に書籍とかJSUGメーリングリストとかブログ各所とかいろんな所で書かれているのであえてここで殊更に詳しくは書きませんが、大別してSpringとStrutsを連携させるには以下の三つの方法があると思います。

  1. ActionSupport系の機能を使う
  2. DelegatingActionProxy系の機能を使う
  3. DelegatingRequestProcessor系の機能を使う

1.の手法では、Springが提供するActionSupport系のクラスを継承してStrutsのActionを作成します。DispatchActionSupportとかMappingDispatchActionSupportとか、Strutsの各種Actionに対応する形でいくつかのActionSupportがあります。お手軽で、Actionの設定はstruts-config内で完結するので、3つの手法のうちで設定量が一番少なくてすむ反面、三つの手法のうち唯一、依存するクラス(サービス等)を注入されるのでは無く、依存するクラスを自ら取りに行くところが、やや微妙です。即ち、ありていに言えば、三つの手法で唯一

実はDIパターンになっていない

ところがミソです。Actionの管理がstruts-config内で完結している(SpringのBean定義ファイル上で管理されていない)ので、当然SpringのAOP対象外であるところも見逃せない微妙なところです。


2.の手法では、各機能ごとに実装者が作成する真の(?)ActionはSpringのBean定義ファイル上に定義し、struts-config上にはSpringが提供するDelegatingActionProxyを定義します。即ち、HTTPリクエストは一旦DelegatingActionProxyで受けて、その後DelegatingActionProxyからSpring管理の真のActionに処理が委譲される感じです。

設定イメージはこんな感じです。

 <action path="/sample" type="org.springframework.web.struts.DelegatingActionProxy" name="sampleForm">
     <forward name="success" path="sample.jsp" />
     (略)
 </action> 

(struts-config)

 <bean name="/sample" class="jp.co.(略).web.action.SampleAction">
     <property name="sampleService" ref="sampleService" />
     (略)
 </bean> 

(Bean定義ファイル)

1.の手法と違い、真のAction(例中のSampleAction)自体は「DIパターン」になっていて、ActionもSpring管理なのでAOPを適用できたり、Bean定義ファイルのいろんな設定機能(プロパティのインジェクションとか定数のインジェクションとか)を使えたり、色々機能豊富な反面、1つのActionにつき二個も設定があるのは結構面倒です。


3.の手法では、真のActionをSpring上で管理するところは2.と同じですが、struts-config上にはActionを用意せずに、RequestProcessorから直接Spring上のActionを呼び出すところが2.と違います。

ただし

struts-configにタグの設定をしなくて済むようになる訳ではありません。以下の設定イメージに示すように、

 <action path="/sample" name="sampleForm">
     <forward name="success" path="sample.jsp" />
     (略)
 </action> 

(struts-config)

 <bean name="/sample" class="jp.co.(略).web.action.SampleAction">
     <property name="sampleService" ref="sampleService" />
     (略)
 </bean> 

(Bean定義ファイル)

タグのtype属性を省略できるようになるだけであり、タグ等のStruts的な設定はやっぱりstruts-config上で設定しなければならないのは2.の手法と同様で、正味のところ設定量は2.も3.もそんなに変わりません。加えて、3.の手法の痛いところは、2.の手法や1.の手法と違って、Struts 1.3のComposableRequestProcessorの機能を使えないところです。ComposableRequestProcessorは、従来のStrutsと違って、RequestProcessorの挙動をカスタマイズしたくなったときはちょろっとAction Commandを足したり引いたり差し替えたりするだけで済むので従来と比べて個人的には随分楽になって重宝しているのですが、DelegatingRequestProcessorだとその恩恵が受けられません。

Spring2.0からは、似たようなRequestProcessor系のStruts-Spring連携機能としてAutowiringRequestProcessorが追加されたようですが、ComposableRequestProcessorの機能がつかえないのは相変わらずのようです。

Spring2.5で新しく何か追加されていないかとそれらしきクラスを探してみたのですが、私のみたところ、それらしきクラスはどうも無いようです。


即ち

結論から言うと、三つの手法はそれぞれ三者三様であり、それぞれいいところも悪いところもあって、どれが一番良いと言えないというか、好みの問題的な違いと言うか、まとめると多分こんな感じです。

手法 メリット デメリット
1 割とお手軽。設定量が少ない。 厳密に言うとDIではない。AOPとかも使えず、Springの機能は一番活かしづらい。
2 1.と違ってAOP等のSpringの機能も活かせ、3と違ってStruts1.3の機能も使える 設定量は一番多い。
3 1.と違ってAOP等のSpringの機能も活かせ、2よりも若干設定量は少ない。 Struts1.3の機能は使えない。


とても長い前フリでしたが、ようするに、私はこういう手法が欲しいと思う訳です。

  1. 設定量は手法1と同程度
  2. ActionはSpringのBean定義の管理下に置き、AOP等のSpring的な機能を活用できる
  3. Struts1.3の機能であるComposableRequestProcessorを活用できる

上記のうち2番目と3番目は、実のところ、Strutsの「org.apache.struts.chain.commands.servlet.CreateAction」を継承して、「DelegatingRequestProcessor」や「AutowiringRequestProcessor」と同様のAction生成ロジックを行うようにgetAction()メソッドをオーバーライドすれば、とりあえず手法3と同様の記述量で、かつStruts1.3の機能を利用できるようにはなります。

問題は、

  1. 設定量は手法1と同程度

これです。これを解決する手法は・・・

そう

アノテーション

です。正直なところ、私はSpringのアノテーションインジェクションについては若干懐疑的でした。設定が楽になるのはいいのですが、Spring管理下のサービスは、Spring自身にすら依存していないところが一つの謳い文句であったような気がしていたからです。ビジネスロジックにSpringが提供するアノテーションを付加して、XMLの記述が要らなくなる代わりに、ビジネスロジックがSpringに依存するようになってしまうのは正直どうかと思っていた訳です。

しかし

これがActionならば話は別です。Actionは当然ながら元々Strutsに依存しています。それが今更Springへの依存が増えたからといってどうだというのでしょう。というか、手法1でもそもそもActionがSpringに依存しているので、ビジネスロジックがSpringに依存してしまうことに比べれば、ActionがSpringに依存してしまうようになるのはたいした問題では無いというか場合によっては今まで通りです。

そんな訳で

私は第四の手法として、以下のようにStrutsとSpringを連携させるのがよいのではないだろうかという結論に達しました。

  • StrutsとSpringの連携は、「org.apache.struts.chain.commands.servlet.CreateAction」を継承してAction Commandを自作して行う。
    • Actionの生成ロジックは、「AutowiringRequestProcessor」と同じものが良さそうかな。
  • ActionのStruts的な設定はstruts-config上で行い、ActionのSpring的な設定はアノテーションで行う。
    • イメージ的にはこんな感じ。
package xxxx;

import javax.annotation.Resource;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.springframework.stereotype.Controller;

@Controller(value = "sampleAction")
public class SampleAction extends Action {
    private SampleService sampleService = null;
    
    @Resource(name = "sampleService")
    public void setSampleService(SampleService sampleService) {
        this.sampleService = sampleService;
    }
  
    (略)
}
    • 「value = "sampleAction"」はSampleAction自体のBean IDの定義であり、「name = "sampleService"」は注入するサービスのBean IDっぽい。