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ってそういう小さな工夫がいっぱい詰まってて、それらを設定ファイル
で柔軟に制御することが出来るんだなぁと感じました。