このシリーズの最後の記事では、ユーザインターフェイスを Struts から Struts2 に移行して、アプリケーションを完全に変換します。パート I (source)では、アーキテクチャの概要、および Struts と Struts 2 のアプリケーションの基本的な違いを説明しました。パート II (source)では、アクションや設定など、現実のアプリケーションの変換について説明しました。
高いレベルからみると、Struts2 のタグの多くは、Struts に存在するタグとよく似ています。ロジックタグ、bean からのデータを表示するタグ、およびフォームをサポートして bean データを特定のフィールドに事前設定するタグがあります。細かくみると、違いがあることがわかります。
この違いにより、新しいフレームワークでは次のような機能が実現されています。
- JSP タグが、さまざまな表示テクノロジをファーストクラスオブジェクトとして表示から使用できるようになりました。- これまで、使用できるタグライブラリはJSPに限られていました。Struts2 では、すべての表示テクノロジ (JSP、Velocity、Freemarker など) をファーストクラスシチズンとし、オブジェクトを処理できるようにするという方法が採用されています。これにより、Web アプリケーションの開発はより簡単になります。
- テーマ - グローバルタグまたは個別タグにテーマを設定することができるようになりました。
これらの機能は、ユーザインターフェイス層のアーキテクチャで上記の方法を採用したことにより利用できるようになったものです。
ユーザインターフェイスアーキテクチャ
読者の多くの方は、モデル・ビュー・コントローラのパターンを既に理解されていることでしょう。これは、Struts および Struts2 どちらのフレームワークでも基本となっています。Struts2 では、このパターンを、別の部分で、つまりユーザインターフェイスアーキテクチャの基本として使用しています。
高レベルからみていくと、次のようになります。
モデル - Component 基本クラスから拡張されたモデル。UI アーキテクチャのモデルには、追加の役割があります。モデルは、実装に依存しないデータコンテナでありながら、使用されるテンプレートを指定することにより、それ自体をどのようにレンダリング (つまり表示) するかという情報を提供します。
ビュー - テンプレートによって生成される最終的な HTML。通常は、タグライブラリのJava コードが、必要なロジックを提供し、HTML コードを出力ストリームに書き込んでいました。この機能をテンプレート言語に任せられるようにすることは、柔軟性の向上につながります。
ヒント: Struts2 では、デプロイメント使用できるテーマが複数用意されており、タグの表示をレンダリングする場合、デフォルトでは、xhtml テーマが使用されます。xhtml テーマは、テーブルを使用したレイアウト、検証メッセージの表示、およびラベルと値の列での表示、フォーム要素のための機能を提供します。この他の使用可能なテーマとしては、css_xhtml (テーブルレイアウトではなく、CSS レイアウトを使用する)、simple (付加的機能のない、基本的な HTML タグ表示)、および ajax (Ajax可能なタグ表示) があります。theme 属性に値を指定することで、これらのいずれのテーマも表示コードで使用することができます。
.さらに、カスタムフレームワークのコンパイルなしで、開発者がまったく新しいテーマを作成することもできます。これについては、この記事では取り上げません。詳細はStruts2 wiki を参照してください。
コントローラ - タグライブラリにアクセスする表示言語それぞれについて、新しいコントローラオブジェクトが提供される必要があります。Struts2 がデフォルトで提供する実装は、AbstractDirective (Velocity用)、TagModel (Freemarker用)、および ComponentTagSupport (JSP用) です。これにより、これまでは JSP のみで使用可能であったタグライブラリ機能が、他の表示テクノロジでもファースクラスシチズンの1つとなります。
これらはすべて、現時点では少し抽象的に感じられることでしょう。タグに関するより詳しい説明を進めながら、具体的なアプリケーションを明らかにしていきたいと思います。まずは、いくつかのコードを確認します。
JSP の変換
最初に、追加用の JSP を見てみましょう。以下に、Struts バージョンのものを示します。
<html>
<head><title>Add Blog Entry</title></head>
<body>
<form action="save.action" method="post">
Title: <input type="text" name="title" /><br/>
Entry: <textarea rows="3" cols="25" name="entry"></textarea> <br/>
<input type="submit" value="Add"/>
</form>
</body>
</html>
既におわかりのとおり、これは、純粋な HTML ページです。add.html と呼んでもよいページです。
以下は、Struts2 バージョンのものです。
<%@ taglib prefix="s" uri="/WEB-INF/struts-tags.tld" %>
<html>
<head><title>Add Blog Entry</title></head>
<body>
<s:form action="save" method="post" >
<s:textfield label="Title" name="title" />
<s:textarea label="Entry" name="entry" rows="3" cols="25" />
<s:submit value="Add"/>
</s:form>
</body>
</html>
ここでは JSP を使用するので、最初の行で、そのタグライブラリを定義します (これは、これ以下のコードでは省略しています)。struts-tags.tld ファイルは、使用可能なタグを定義するもので、WEB-INF ディレクトリにあります。このファイルは、もともと、struts2-core-2.0.0-SNAPSHOT.jar ファイルから抽出したものです。
Struts のサンプルを見ると、他の違いは HTML タグに関係していることがわかります。form タグは s:form に、textarea は s:textarea に、および input タグは s:textfield または s:submit タグに変わっています。Struts2 タグの名前は、可能な限り、対応する HTML タグに一致するようになっています。ただし、input タグは、type 属性が追加されているのではなく、名前が変わっています。type が、タグ自体の名前になっています。
Struts2 タグを使用すると、コードが少し簡略化され、読みやすくなります。s:form タグでは、action 属性の値が save.action ではなく、save になっています。拡張子は、Web アプリケーションのコンテキストに応じて、タグが追加します。さらに、必要に応じて、namespace 属性で、追加のパス情報を指定することもできます。この情報も、タグによって、表示される最終的な HTML フォームタグのマークアップに埋め込まれます。
さらに、label 属性が追加されていることも変更点です。Struts2 タグでは、各フォーム要素のラベルに使用されるテキストが、タグの外側ではなく、タグの一部として指定されています。なぜこのようになっているのでしょうか。その答えは、タグの表示に関連します。記事の冒頭で、UI アーキテクチャについてと、モデルがそれ自体をどのように表示するかについて説明しました。Struts2 add.jsp が生成した HTML のソースを表示してみると、フォームおよびすべての要素がテーブルタグの中にあることがわかります。ラベルは、テーブルの1つの列に配置され、input または textarea の HTML タグも、また別の列に配置されています。デフォルトの xhtml テーマが、追加のレイアウト設定を提供しています。
これは追加用のページであるので、最後にもう1つ説明します。ユーザが入力した値が、アクションまたはモデルのどのフィールドに設定されるかは、name 属性の値で判断されます。このため、name 属性を title とする場合は、setTitle(...) メソッドを用意しておく必要があります。
view.jsp には、また別のよく使用されるタグがあります。データの値を表示するタグです。
Struts 表示用JSP:
<html>
<head><title>View Blog Entry</title></head>
<body>
Id: <c:out value="${requestScope.blog.id}" /><br/>
Title: <c:out value="${requestScope.blog.title}" /><br/>
Entry: <c:out value="${requestScope.blog.entry}" /><br/>
Updated: <fmt:formatDate
value="${requestScope.blog.created.time}" pattern="MM/dd/yyyy"/><br/>
<br/>
<a href="list.action">Back to List</a> |
<a href="edit.action?id=<c:out value="${requestScope.blog.id}"/>">Edit</a>
</body>
</html>
Struts2 表示用 JSP:
<html>
<head><title>View Blog Entry</title></head>
<body>
Id: <s:property value="id" /><br/>
Title: <s:property value="title" /><br/>
Entry: <s:property value="entry" /><br/>
Updated: <s:property value="created.time" /><br/>
<br/>
<s:url id="viewUrl" action="list" />
<s:a href="%{viewUrl}">Back to List</s:a> |
<s:url id="editUrl" action="edit" >
<s:param name="id" value="%{id}" />
</s:url>
<s:a href="%{editUrl}" >Edit</s:a>
</body>
</html>
JSTL では、c:out タグの value 属性に、表示するデータを指定します。これは、静的な値にすることも、動的に評価される値にすることも可能です。式は、区切り文字として ${ and } を使用して指定します。表示用 JSP では、HTTP 要求スコープ内のオブジェクトインスタンスにアクセスすることが必要になる場合があります。これは、式の最初に requestScope を指定することにより実行できます。スコープを指定した後に、オブジェクトグラフのパスはドットを使用して指定します。
補足:ドットによる表記は、オブジェクトグラフの中のパスを示す簡単な方法です。getPerson().getAddress().getPostcode() という Java コードを使用する代わりに、単純に person.address.postcode と記述することができます。
fmt:formatDate タグは、c:out タグに似ています。違いとしては、c:out は時間をミリ秒単位で表示し、fmt:formatDate タグはデータをよりユーザにわかりやすい形で表示します。
補足: JSTL の使用は、JSP からデータを使用する方法のうちの1つに過ぎません。他の方法としては、JSP タグを直接的に使用する方法 (たとえば、jsp:useBean および jsp:getProperty など)、および Struts Taglib タグライブラリを使用する方法などがあります。Struts2 タグライブラリと同様、Struts Tablib タグライブラリには、HTML タグに基づく付加的な抽象概念、および実装をより簡単にする付加的なマッピング機能があります。
Struts2 の s:property タグは、c:out タグに非常に似ています。これは、値を取得するために式言語を使用します。値は、ドット表記を使用してオブジェクトグラフを検索することによって特定することができます。ただし、2つの大きな違いがあります。1つ目は、OGNL (Object Graph Navigational Language (http://opensymphony.com/ognl) が式言語として使用されていることです。このため、表示する値を特定するのに、ドット表記だけでなく、オブジェクトメソッドの呼び出し、投影、およびラムダ式などの高度な機能を使用できます。
OGNLは、オープンソースプロジェクトの1つであり、Struts2 とは別に開発されています。
2つ目の違いは、スコープが必要ないことです。これは、利点でもあり、欠点でもあります。Struts2 には、値スタック (Value Stack) があり、JSP 開発者はさまざまなスコープを明示的に指定する必要がありません。値スタックは、値を探すときにたどるさまざまなスコープの順序を表します。値を探す場合、フィールドの getter が、あるスコープの中で見つからない場合は、次のスコープを検査します。これを、フィールドが見つかるか、すべてのスコープを検査し終わるまで続けます。スコープは、次のような順序になっています。
- 一時オブジェクト - これは、JSP ページで作成されたオブジェクトです (この後、s:url タグで例を説明します)。または、有効期間の短いタグによって作成されたオブジェクト (コレクションをループしている際の現在のオブジェクトなど) です。
- モデルオブジェクト - モデルオブジェクトが使用されている場合は、モデルオブジェクトが次に検査されます (アクションの前です)。
- アクションオブジェクト- tこれは、実行されたアクションです。これにより、セッションまたは要求のスコープ内に明示的にアクションを置かなくても、アクションのデータにアクセスできます。
- 名前指定オブジェクト - このオブジェクトは、#application、#session、#request、#attr および #parameters を含み、それぞれに対応するサーブレットスコープを参照します。
利点は明らかです。探しているフィールドについて各スコープが順番に検査されていきます。しかし、モデルオブジェクトとアクションの両方に同じフィールド名が存在する場合 (たとえば id など) には、問題が生じることがあります。モデルの値を求めている場合は、正しい結果が返されます。しかし、アクションの値を求めている場合でも、モデルの値が返されます。モデルスコープの方が、アクションスコープよりも順序が前であるためです。この問題を解決するには、id フィールドをスタックのどこから取得したいのかを示す修飾子を指定します。この例では、[1].id と記述することで、アクションの値を取得できます。詳細については、http://cwiki.apache.org/WW/ognl-basics.html を参照してください。
最後は、HTML の a タグです。JSTL から考えると、Struts JSP は、単純に標準 HTML の a タグを使用しており、JSTL の c:out タグで編集するブログの id を提供していました。Struts2 の実装は、少し複雑になります。その部分だけもう一度以下に示します。
<s:url id="editUrl" action="edit" >
<s:param name="id" value="%{id}" />
</s:url>
<s:a href="%{editUrl}">Edit</s:a>
最初の s:url タグで、使用する URL を生成します。このタグは、s:form タグと同じ方法で、アクションの属性 (表示されています) および名前空間 (表示されていません) を使用して生成を行います。id という属性もあります。これには、重要な役割があります。s:url タグは、生成した URL を、id 属性で指定された値を ID として、一時オブジェクトスコープ内に置きます。名前と値のセット (?user=bob という部分) を URL に追加するには、s:param タグが使用されます。
補足: 単に s:property タグを HTML の a タグで使用することはできないでしょうか。答えは、できます。上記の方法の利点は、アプリケーション全体で一貫した URL を生成できるという点です。
Struts2 の高度な機能では、アクションを呼び出す URL を変更することができます。新しい ActionMapper 実装を使用することで、/listUser.action?id=4といった通常の URL ではなく、/user/4/list といったよりわかりやすく、おそらく Ruby On Rail により近い形の URL を生成することができます。このような変更を活用するためには、すべてのコードが1つの標準的な方法で URL を生成できる方が有利です。
s:a タグは、機能的には HTML の a タグと同じですが、href 属性の値を値スタックから取得できるという点が異なります。ここでは、編集のページ用に生成された URL の値を取得する必要があります。s:param タグおよび s:a タグのどちらも区切り文字として を使用しますが、s:property はこれを使用しません。a タグが常に値スタックから値を抽出すると想定できる場合 (s:property タグの場合)、区切り文字は必要ありません。しかし、値の可能性として、静的テキスト、値スタックからの値、評価される式、またはこれらのオプションの組み合わせ (s:param と s:a タグ) が考えられる場合は、区切り文字が必要となります。しかし、心配する必要はありません。必要でない場合に区切り文字を使用しても、Struts2 は単にそれを無視します。
これで、フォームデータを入力し、入力された情報をフォームで表示するというところまで説明しました。次は、フォームデータを編集します。これは、add.jsp コードと非常に似ています。HTML 入力フィールドを持つ HTML フォームです。違いは、現在の情報を表示するために追加のタグがいくつか必要になることと、どのレコードを編集するのかを指定する手段が必要になることです。
以下は、Struts の編集用 JSP です。
<html>
<head><title>Update Blog Entry</title></head>
<body>
<form action="update.action" method="post">
<input type="hidden" name="id"
value="<c:out value="${requestScope.blog.id}" />" />
Id: <c:out value="${requestScope.blog.id}" /><br/>
Title: <input type="text" name="title"
value="<c:out value="${requestScope.blog.title}" />"/><br/>
Entry: <textarea rows="3" cols="25" name="entry">
<c:out value="${requestScope.blog.entry}" /></textarea><br/>
Updated: <fmt:formatDate
value="${requestScope.blog.created.time}" pattern="MM/dd/yyyy"/><br/>
<input type="submit" value="Update"/>
</form>
</body>
</html>
以下は、Struts2 の編集用 JSP です。
<html>
<head><title>Update Blog Entry</title></head>
<body>
<s:form action="update" method="post" >
<s:hidden name="id" value="%{id}" />
<tr>
<td align="right">Id:</td>
<td><s:property value="id" /></td>
</tr>
<s:textfield label="Title" name="title" value="%{title}" />
<s:textarea label="Entry" name="entry" value="%{entry}" rows="3" cols="25" />
<tr>
<td align="right"> Updated:</td>
<td><s:property value="created.time" /></td>
</tr>
<s:submit value="Update"/>
</s:form>
</body>
</html>
これは、今回想定しているものを正確に実現しています。input HTML タグには value 属性が追加されています。この実際の値は、フィールドに現在の内容を表示する c:out タグです。また、textarea HTML タグも、開始タグと終了タグの間に c:out タグを含んでいます。編集するレコードの情報は、hidden HTML タグによって次のアクションに伝えられます。このタグにも、c:out タグがあり、編集するレコードの id 値を指定しています。
この Struts2 バージョンは、今まで以上に edit.jsp と似ています。各入力タグには、対応する HTML タグと同じように value 属性が追加されており、区切り文字 (値が静的である可能性があるため)、および値スタック (上記で説明したとおり) を使用しています。また、s:hidden タグもあります。これは、予想どおり、hidden HTML タグと同じです。
この JSP のあまり良くない点は、いくつかのフィールド (id および updated) は、Struts2 で提供されるテーマでそれらを正常に表示するために HTML タグ情報を必要とすることです。これを必要としないフィールドもあります。これには、いくつかの解決策があります。1つは、simple テーマを使用し、すべてのレイアウト情報を JSP で提供するという方法です。また、表示のみで編集を不可とする s:input タグ用に新しいテンプレートを作成するという方法もあります。これは、読者の皆さんへの課題としておきましょう。
最後の JSP は、一覧用の JSP です。この JSP では、ロジックを提供する、新しいカテゴリのタグライブラリを紹介します。ここで新たに紹介するものは、このタグライブラリのみです。これを、既に説明したデータ表示のタグライブラリと組み合わせて使用します。
以下は、一覧用 JSP です。
<html>
<head><title>List Blogs</title></head>
<body>
My Blogs:<br/> <c:forEach var="blog" items="${requestScope.bloglist}">
<a href="view.action?id=<c:out value="${blog.id}"/>">
<c:out value="${blog.title}"/></a>
[ Updated <fmt:formatDate value="${blog.created.time}" pattern="MM/dd/yyyy"/> ]
(<a href="remove.action?id=<c:out value="${blog.id}"/> ">remove</a>)
<br/>
</c:forEach>
<a href="add.action">Add a new entry</a>
</body>
</html>
以下は、Struts2 の 一覧用 JSP です。
<html>
<head><title>List Blogs</title></head>
<body>
My Blogs:<br/>
<s:iterator value="bloglist" >
<a href="view.action?id=<s:property value="id"/>"><s:property value="title"/></a>
[ Updated <s:property value="created.time"/> ]
<s:url id="removeUrl" action="remove" >
<s:param name="id" value="%{id}" />
</s:url>
<s:a href="%{editUrl}" >Edit</s:a>
(<s:a href="%{removeUrl}">remove</s:a>)
<br/>
</s:iterator>
<s:url id="addUrl" action="add" >
<s:a href="%{addUrl}">Add a new entry</s:a>
</body>
</html>
JSTL のタグは、c:forEach タグです。これには2つの属性があります。var 属性は、コレクション内の現在の要素を参照するための名前を指定します。items 属性は、繰り返すコレクションを指定します。既に予想されていると思いますが、c:forEach タグで囲まれた c:out タグは、すべて blog で始まっています。つまり、このタグは現在の要素を参照しています。
同様に、Struts2 の実装には、s:iterator タグがあります。これの機能は、JSTL の c:forEach タグとまったく同じです。しかし、コレクションの現在の要素を参照するために指定する属性はありません。これの理由も、値スタックに関係します。現在の要素は、一時オブジェクトスコープにあり、値スタックの最上位に置かれています。このため、特定のオブジェクト id を指定しない s:property タグでアクセスすることができます。オブジェクト id を指定する方が安全と思われる場合は、c:forEach タグの var 属性と同様の機能を持つ id 属性を使用することができます。
アクションの簡略化
前回の記事(記事)では、アクションの詳細を説明しました。すべてのアクションメソッドの最後のステップは、Blog インスタンスを HttpServletRequest に置くことであったことを覚えているでしょう。しかし、#request を、s:property タグの値の先頭に付けることはしていませんでした。
#request が値の先頭にない理由はおわかりのことでしょう (モデルとアクションは、値スタックに置かれています)。残されている作業は、不要なコードを取り除いて、アクションクラスを簡略化することです。具体的には、Blog を HttpServletRequest に置く必要はありません。また、アクションは、ServletRequestAware インターフェイスを実装する必要がありません。
ListBlogsAction アクションにも同じ変更が必要となります。ただし、ListBlogsAction は、モデルオブジェクトを使用していないので、getBloglist() メソッドをこのアクションに追加する必要があります。
完全なソースは、この記事から入手できます(source)。アクションクラスの変更については、そこで詳細を確認してください。
まとめ
この記事では、設計と実装の両方の観点から多くのことを説明してきました。しかし、簡単な説明で済ませた部分もあります。特に、使用可能な多数のタグを説明するまでの時間はありませんでした。Struts2 のタグは、4つのカテゴリ (コントロールタグ、データタグ、フォームタグ、および非フォーム UI タグ) に分類することができます。これらについての詳細は、Struts2 のタグリファレンス (http://struts.apache.org/WW/tag-reference.html) で確認できます。
タグだけでなく、ここで取り上げることのできなかった機能もあります。以下に、そのいくつかを挙げておきます。興味のあるものについては、調査してみることをお勧めします。
- 検証 - Struts2 では、検証コードを、クラスで、またはアクションクラスやモデルクラスに対して機能する宣言的検証フレームワークを使用して、提供することができます。また、Ajax ベースの検証も使用できます。詳しくは、java.net (http://today.java.net/pub/a/today/2006/01/19/webwork-validation.html) の Zarar Siddiqi の記事を参照してください。
- 国際化 - Struts2 では、その前身と同様、アクション、検証、メッセージ、およびタグライブラリに、強力な国際化機能が組み込まれています。
- Ajax サポート - Ajax 機能を提供するために作成された完全なテーマがあります。HTML ページの一部ではなく、XML および JSON の応答を返す手段もあります。
- 結果および結果の種類- 結果を簡単にカスタマイズできます。図、グラフ、JSON、XML、または考え得る他の形のものを結果とすることができます。
- 注釈- 設定を簡単にするため、注釈および「設定に勝る規約」(convention over configuration) に変更が加えられています。これは、XML 設定の必要性をなくすことを最終的な目標としています。
これで、このシリーズは完了です。ここまでの内容で、Struts2 アーキテクチャ (全体およびユーザーインターフェイスの両方のアーキテクチャ)、要求処理の違い、Struts2 アプリケーションの設定方法、アクションと JSP の連結方法などを理解できたことでしょう。
Struts についての事前知識と、この Struts2 に関する記事のシリーズから得た情報によって、Struts2 を最初から新規に作成する方法に加えて、複雑なアプリケーションを移行する方法も理解することができたことと思います。
著者について
Ian Roughley は、マサチューセッツ州ボストンを拠点とする独立コンサルタントであり、講演や執筆などの活動も行っています。彼は、フォーチュン 10 社から創業間もないクライアントまで、さまざまな規模のクライアントに対して、アーキテクチャ、開発、プロセス改善および指導サービスを 10 年間にわたり提供してきました。彼は、実用的で成果重視の手法に注目し、オープンソースや、アジャイル開発技法によるプロセス改善および品質改善を支持しています。
原文はこちらです:http://www.infoq.com/articles/migrating-struts-2-part3
(このArticleは2006年12月5日にリリースされました)