2004/03
WebWorkについて簡単に説明すると、WebWorkは、JavaオープンソースプロジェクトOpenSymphonyによって開発されている
Webアプリケーションフレームワークです。
特徴は、
・シンプル
・Servlet APIを使わないActionを作れる
・柔軟な設定ファイル
・Velocityをサポート
といったところです。
仕事で使うってわけではないのですが、おもしろそうだったので使ってみました。
いろいろと特徴があるとはいえ、Webアプリケーションフレームワークなので
Strutsと似てます。なので簡単に使うことが出来ました。
使ってみての感想は、「設定ファイルにオプションがいっぱいあるなぁ」と
いった感じです。とはいえ、ActionでServlet APIを使わないので
今まで覚えたServletテクニックが使えないというデメリットがあります。
「Servletでは、こうやったけど、WebWorkではどうやるんだ?」みたいなね。
StrutsはServletと同じでrequestとresponseを引数にもらうから
同じやり方で実装出来るんですけどねぇ。
ですが、ActionでServlet APIをつかわないことにはとてもメリットが
あります。それは、
「Actionのテストが簡単!!」
普通にJUnitでテスト出来ます。APサーバに配備しなくても動くんですな!
ということで今回はActionクラスを作ってJUnitでテストします。
ActionクラスはStrutsで掲示板で作ったののWebWork版を作ります。
パッケージ名だけ変えてクラス名はいっしょ。
それではソース行ってみよう!
package jp.gr.java_conf.yamarou.app.forum.webwork; import com.opensymphony.xwork.ActionSupport; import jp.gr.java_conf.yamarou.app.forum.*; /** * 掲示板メッセージを読み込むAction。 */ public class LoadMessageAction extends ActionSupport { private ForumMessageVO[] messages; public String execute() throws Exception { ForumDAO dao = new ForumDAO(); try { this.messages = dao.getForumMessages(); } catch (Exception ex) { super.addActionError(super.getText("error.system")); } return SUCCESS; } public ForumMessageVO[] getMessages() { return messages; } } package jp.gr.java_conf.yamarou.app.forum.webwork; import com.opensymphony.xwork.ActionSupport; import jp.gr.java_conf.yamarou.app.forum.*; /** * 送信された名前、メッセージを保存するAction。 */ public class SaveMessageAction extends ActionSupport { private String name; private String message; protected void doValidation() { if (name == null || name.equals("")) { super.addFieldError("name", getText("error.name.required")); } if (message == null || message.equals("")) { super.addFieldError("message", getText("error.message.required")); } } public String doExecute() throws Exception { ForumDAO dao = new ForumDAO(); dao.save(this.name, this.message); return SUCCESS; } public void setMessage(String message) { this.message = message; } public void setName(String name) { this.name = name; } public String getMessage() { return message; } public String getName() { return name; } } package jp.gr.java_conf.yamarou.app.forum.webwork; import jp.gr.java_conf.yamarou.app.forum.*; import junit.framework.*; public class ActionTest extends TestCase { public static void main(String args[]) { junit.textui.TestRunner.run(new TestSuite(ActionTest.class)); } public ActionTest(String name) { super(name); } public void testAction() throws Exception { final String NAME = "yamarou"; final String MSG = "hello"; SaveMessageAction saveAction = new SaveMessageAction(); saveAction.setName(NAME); saveAction.setMessage(MSG); saveAction.execute(); LoadMessageAction loadAction = new LoadMessageAction(); loadAction.execute(); ForumMessageVO[] messages = loadAction.getMessages(); assertEquals(NAME, messages[0].getName()); assertEquals(MSG + System.getProperty("line.separator"), messages[0].getMessage()); } }
Actionがやっている事はStruts編のActionと同じです。クラスの役割分担も
同じ。ただ、Validatorを使わなかった(WebWorkにもValidatorはある)ので、
入力チェックのコードが入っています。
SUCCESSって定数が親クラスで定義されている辺りが芸が細かいというか
痒い所に手が届くといった感じです。Strutsで"success"ってリテラルで書くのも
なんだし、かといって自分で定数作るのもばかばかしいなぁと思ってたので、
いいなーと思いました。他にもINPUTとか良く使いそうな定数が定義されています。
あと、入力エラーで
super.addFieldError("message", getText("error.message.required"));
のコードが実行されると、そのフィールドのすぐ上にエラーメッセージが
表示されるのが面白かったです。完成したらダウンロード出来るように
するのでぜひ実際に動かしてみてください。「オッ」っと思います。
それから、JUnitのTestCaseのコード(ActionTest)がありますが、普通に
コンソールから実行出来ます。簡単にテストが出来ました。(エラーメッセージの
テストは出来ないが)
ActionTestを実行したらバグが見つかりました。
ActionTestの
assertEquals(NAME, messages[0].getName());
のmessages[0].getName()の戻り値の末尾に改行が含まれているというバグ
でした。まぁ改行あっても見た目上変わらないですけどね、掲示板はHTMLだから。
なので、ForumDAOに41行目を
nameList.add(line.substring(9));
から
nameList.add(line.substring(9, line.length()));
に変更しました。
WebコンテナなしでActionをテスト出来るのは良い気がしますが、
テストするActionがLocalインターフェースのEJBにアクセスしていたら
結局、J2EEサーバに配備しなければ動かないわけなので、
LocalインターフェースのEJBを使用しない場合にはメリットがあるかなぁ
と感じました。
でもって、設定ファイル(xwork.xmlの一部抜粋)
class="jp.gr.java_conf.yamarou.app.forum.webwork.SaveMessageAction">
<result name="input" type="dispatcher">
<param name="location">/forum.jsp</param>
</result>
<result name="success" type="chain">
<param name="actionName">loadMessage</param>
</result>
<result name="error" type="dispatcher">
<param name="location">/forum.jsp</param>
</result>
</action>
<action name="loadMessage"
class="jp.gr.java_conf.yamarou.app.forum.webwork.LoadMessageAction">
<result name="input" type="dispatcher">
<param name="location">/forum.jsp</param>
</result>
<result name="success" type="dispatcher">
<param name="location">/forum.jsp</param>
</result>
<result name="error" type="dispatcher">
<param name="location">/forum.jsp</param>
</result>
</action>
ここで
<result name="success" type="chain">
ってのがありますが、resultのtypeをdispatcherとすると
forwardしてchainにすると次のActionを実行するようです。
dispatcherにActionのパスを書いても次のActionを実行するわけですが(Strutsは
これしかない)そうするとforwardのオーバーヘッドが多少発生します。
chainと書くとforwardのオーバーヘッドがないのだと思います。
まさに僕がVol16でかいた「Chain of Action」パターンに最適です。
WebWorkってそういう小さな工夫がいっぱい詰まってて、それらを設定ファイル
で柔軟に制御することが出来るんだなぁと感じました。