はじめに
StrutsはWebアプリケーションの開発を容易にする、Javaサーブレットや関連技術をベースとした、オープンソースフレームワークです。Strutsは、Webアプリケーション開発者が直面する多くの共通課題に解決策を提供するため、小規模なプロジェクトからから大規模なエンタープライズ アプリケーションまで、さまざまな種類のアプリケーション開発において広く採用されています。
Strutsの利用は、開発者のタスクを容易にはするものの、アプリケーションを使用するエンドユーザー体験の向上にはほとんど意味をなさないといってもいいでしょう。Strutsを使って開発されたアプリケーションといえども、HTTPリスポンス/リクエストの仕組みを利用した、HTMLのアプリケーションであることに変わりはありません。
デベロッパー開発者だけでなく、企業も、HTMLでは実現困難の限界を超えて、より魅力的なユーザー体験が提供できるソリューションを求めており、このニーズは日増しに高まっています。
開発者だけでなく、企業も、HTMLの限界を超えて、より魅力的なユーザー体験が提供できるソリューションを求めており、このニーズは日増しに高まっています。
Macromedia Flexは、このようなニーズを満たしながら、デスクトップアプリケーション(RIA)の利便性とWebアプリケーションの広範囲にわたる配信性を兼ね備えた、次世代のリッチインターネットアプリケーションの開発を可能にするプレゼンテーションサーバーです。
この記事の中で紹介するサンプルコードを実行するためには、以下のソフトウェアをインストールする必要があります。
Macromedia Flex
Flexは Macromedia JRun、IBM Websphere、BEA WebLogic、Apache TomcatなどのJ2EEアプリケーション サーバー上で動作します。将来のバージョンでは、.NETサーバー上でも動作するようになる予定です。また、トライアル版の入手方法については、こちらを参照してください。
MVCアーキテクチャーにおけるViewの改善
StrutsとFlexの両方に共通していえることは、どちらも、Model-View-Control(MVC)アーキテクチャーを元に開発されたソフトウェアであるという点です。
- Strutsは、主にMVCデザインパターンのcontrollerに重点を置いたフレームワークであり、従来のHTML/JSP環境のview構築を支援するためのJSPタグライブラリを提供しています。
- Flexは、主にMVCデザインパターンのViewに重点を置いています。Flexには、コントローラーロジックとしてリモートシステムとの連携や、セキュリティ機能も実装されています。
MVCアーキテクチャの価値は、アプリケーションのコンポーネントを明確、かつ限定されたスコープで作成することによって、これらコンポーネントの再利用性とシステム全体の保守性の向上が実現できることです。さらには、アプリケーションの一部を更新する際に、他の部分への影響を最小限に抑えることができるようになります。Flex/Struts統合は、その価値を拡張し、よりリッチでスマートなビューの代替を提供します。
アプリケーションのビューをFlexを使ったユーザーインターフェイスで完全に置き換えることも可能ですし、HTMLとFlexによる2つのビューを同じアプリケーション、あるいはページ内に共存させることも可能です。例えば、サイトの閲覧関連の要素を従来通りHTMLで提供しつつ、サイトのインタラクティブな部分をFlexアプリケーションで提供するといったことができます。
Flexアプリケーションを分散MVCのViewの一部(Model 2 MVC)と捕らえることもできますが、さらにクライアントサイドで独自のMVCアーキテクチャを実装しています。Flexアプリケーションには、独自のビューコンポーネント(通常MXMLで記述)、モデルコンポーネント(クライアントサイドのデータ)、コントローラーコンポーネント(例えばバックエンドシステムとの通信を行うコンポーネントなど)があります。このようなクライアントサイドでのタスクの分離は、HTMLでは実現できません。リッチインターネットアプリケーションは、複雑なMVC Model2への依存が回避できるという点もあげられます。
Strutsフレームワーク
StrutsはMVCのController部分に重点を置いたフレームワークであり、さまざまなテクノロジーと統合してModelとViewを提供します。Modelの部分の場合、Strutsはデータへアクセスする標準的なテクノロジー、JDBCやEJBなどとやりとりをします。Viewに関しては、JSPページの作成を支援するためのタグライブラリが用意されています。Strutsは、JSPの他、VelocityやXSLT、そしてこの記事で解説するFlexプレゼンテーション サーバーなどのプレゼンテーション技術と連携させることも可能です。
Strutsフレームワークに含まれている、主なコンポーネントは以下の通りです。
ActionServlet | ActionServletはStrutsのコントローラーサーブレットです。StrutsアプリケーションのすべてのHTTPリクエストはこのサーブレットで引き受けます。 |
ActionMapping | ActionMapping オブジェクトは「struts-config.xml」ファイルの中で定義され、以下の点について、それぞれのリクエストをどのように処理するのがが記述されています。・リクエストパス。ActionServletによって、リクエストパスと ActionMapping が関連づけられます。・使用する ActionForm オブジェクト。・使用する Action オブジェクト。・フォワード先のリスト。実際のフォワード先は Action オブジェクトによって選択されます。 |
ActionForm | ActionForm はorg.apache.struts.action.ActionForm クラスを継承したJavaBeansです。クライアントサイドのフォームで入力されたデータから、サーバー上で自動的にActionForm オブジェクトが生成されます。 |
Action | Action はorg.apache.struts.action.Action を継承するクラスで定義され、リクエストに対して実行される機能をコントロールします。Action オブジェクトはJavaBeansやEJBなどのModelオブジェクトと連携し、さまざまなビジネスロジックを実行します。Action オブジェクトは実行結果によって、ActionForward オブジェクトを介し、ActionServletへ次のリクエストフォワード先を通知します。 |
ActionForward | Action オブジェクトの処理後に、リクエストをどこにフォワードするかを指定します。 |
Strutsのワークフロー
従来のStrutsアプリケーションのワークフローは以下の通りです。
- ActionServletがリクエストを受け取ります。
- リクエストされたURIに応じて、ActionServletによって、定義された
ActionMapping
が生成されます。 - ActionServletによって、HTTPリクエストから、
ActionForm
オブジェクトが生成されます。ActionMapping
でデータ検証を行うことが指定されている場合、ActionServletによってActionForm
オブジェクトのvalidate()
メソッドが呼び出されます。 ActionForm
オブジェクトを引数にActionServletがAction
オブジェクトのexecute()
メソッドを呼び出します。このメソッドの呼び出しの戻り値、ActionForward
オブジェクトによって、リクエストの転送先が指定されます。- ActionServletが
ActionForward
オブジェクトによって指定されたリソースにリクエストを転送します。この転送先は一般的にはJSP(ビュー)になりますが、別のAction
へ続けて転送することもできます。 - レスポンスがブラウザに戻ります。
サンプルStrutsアプリケーション
この記事ではFlexとStrutsの連携デモとして、簡単なアプリケーションを例としてとりあげます。このサンプルアプリケーションは、架空のWebサイト上でユーザー登録を行う機能を提供します。
「struts-config.xml」ファイルの中で定義されているAction
は以下の通りです。
<action path="/RegistrationSubmit" type="samples.struts.RegistrationAction" name="registrationForm" scope="request" validate="true" input="/registration/registration.jsp"> <forward name="success" path="/registration/confirm.jsp"/> </action>
下記の図はユーザー登録アプリケーションのワークフローを表したものです。
RegisterAction
クラスのコードは次のとおりです。
package samples.struts; import javax.servlet.http.*; import org.apache.struts.action.*; public final class RegistrationAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ActionErrors errors = new ActionErrors(); RegistrationForm registrationForm = (RegistrationForm) form; String email = registrationForm.getEmail(); // 暫定的なビジネス ロジック: // 通常この部分にビジネス オブジェクトとの処理を配置 if (email == null || email.indexOf("@") < 0) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("errors.registration.failed")); saveErrors(request,errors); return new ActionForward(mapping.getInput()); } request.setAttribute("registration", form); return mapping.findForward("success"); } }
Action
クラスが実装しているビジネスロジック(単純なメールアドレスの検証)はダミーです。現実のアプリケーションでは、データベースへ挿入するビジネスロジックなどを呼び出すなどの処理を行うことになるでしょう。「confirm.jsp」のコードは以下の通りです。
<%@ page import="java.util.Enumeration"%> <%@ taglib uri="/tags/struts-bean" prefix="bean" %> ご登録ありがとうございます<br> <bean:write name="registration" property="firstName"/><br> <bean:write name="registration" property="lastName"/><br> <bean:write name="registration" property="phone"/><br> <bean:write name="registration" property="email"/>
Strutsアプリケーションに対するFlexフロントエンドの実装
Flexを利用すれば、Strutsアプリケーションにリッチなフロントエンドを実装することができます。ここからはFlexとStrutsの統合の実装について、2つの異なる方法を解説していきます。これから紹介する2つの方法は、Flexアプリケーションがリモートシステムと通信を行う2つの方法に対応しています。
- 従来のHTTPリクエスト/リスポンス の仕組みを使用する
- SOAPを利用して、リモート サービスのメソッドを呼び出す
アプローチその1:従来のHTTPリクエスト/リスポンスを使ったインテグレーション
このアプローチでユーザー登録アプリケーションを実装するには、HTMLの代わりにXMLを動的に生成するように「confirm.jsp」を修正するだけです。Flexアプリケーションは、このXMLリスポンスを受け取り、それに応じてユーザー インターフェイスを動的に調整することができます。
ただし、このアプローチでユーザー登録アプリケーションを実装するには、一カ所だけ修正を施す必要があります。当初の「confirm.jsp」はレスポンスとしてHTMLをブラウザに返すように作られていますが、これを、XMLでリスポンスを返すように変更します。Flexアプリケーションは、このXMLリスポンスを受け取ってからユーザーインターフェイスを動的に調整します。
下記の図は、このアプローチを使って実装した場合の動作のワークフローを図解しています。
Flexによるクライアントアプリケーションの実装は以下のコードの通りです。
<?xml version="1.0" encoding="iso-8859-1"?> <mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" height="600"> <mx:HTTPService id="registrationRequest" url="/royale/RegistrationSubmitFlex.do" result="alert(registrationRequest.result.registration.status)"> <mx:request> <firstName>{firstName.text}</firstName> <lastName>{lastName.text}</lastName> <phone>{phone.text}</phone> <email>{email.text}</email> </mx:request> </mx:HTTPService> <mx:Form> <mx:FormItem label="First Name" required="true"> <mx:TextInput id="firstName" width="200"/> </mx:FormItem> <mx:FormItem label="Last Name" required="true"> <mx:TextInput id="lastName" width="200"/> </mx:FormItem> <mx:FormItem label="Phone" required="true"> <mx:TextInput id="phone" width="200"/> </mx:FormItem> <mx:FormItem label="Email" required="true"> <mx:TextInput id="email" width="200"/> </mx:FormItem> <mx:FormItem> <mx:Button label="Register" click="registrationRequest.send()"/> </mx:FormItem> </mx:Form> <mx:Repeater id="errorList" dataProvider="{registrationRequest.result.registration.error}"> <mx:Label>{errorList.currentItem}</mx:Label> </mx:Repeater> </mx:Application>
ソースコードの解説
- HTTPリクエストのページ パラメターと入力用のユーザー インタフェイスのデータは、Flexのデータバインディングを利用して連結します。こうすることで、エンドユーザーがユーザーインターフェイス コントロールに変更を加えると、自動的にリクエストのパラメータも変化します。
id
にerrorList
と指定されたRepeater
はregistrationRequest HTTPService
の戻り値にバインドされています。registrationRequest
がXMLをリスポンスとして受け取ると、Repeater
が自動的に<error>
に対応する文字列を表示します。HTTPService
タグの「result」イベントハンドラーを利用すれば、リスポンスを受け取った際に実行するアクションも指定できます。上記のコードの場合、ユーザー登録の結果を示すメッセージボックスが表示されます。
前述の通り、「confirm.jsp」がHTMLの変わりにXMLをリスポンスとして返すよう変更を加えます。
<registration> <% Object errors = request.getAttribute("org.apache.struts.action.ERROR"); if (errors==null) { %> <status><bean:message key="status.success"/></status> <% } else { %> <status><bean:message key="status.failure"/></status> <% } %> </registration>
FlexとHTMLを同時にサポートする、もう1つの方法
「confirm.jsp」に変更を加えてHTMLの代わりにXMLを生成する以外に、新しく、「struts-config.xml」ファイルにアクションマッピングを作成して、HTMLとFlexの両方を同時にサポートする方法があります。新しいアクションマッピングにはHTMLの場合同様にActionForm
、Action
オブジェクトを使用しますが、リクエストの転送先としては別のJSP(「confirmxml.jsp」)ページを設定します。
<action path="/RegistrationSubmit" type="samples.struts.RegistrationAction" name="registrationForm" scope="request" validate="true" input="/registration/registration.jsp"> <forward name="success" path="/registration/confirm.jsp"/> </action> <action path="/RegistrationSubmitFlex" type="samples.struts.RegistrationAction" name="registrationForm" scope="request" validate="true" input="/registration/confirmxml.jsp"> <forward name="success" path="/registration/confirmxml.jsp"/> </action>
アプローチその2:SOAPを使ったインテグレーション
HTTPリクエスト/リスポンスを使った前述のアプローチその1は、Strutsのワークフローとうまく融合し、エンドユーザーにリッチインターネットアプリケーションの使いやすさを提供することができます。
では、一体なぜ別のアプローチが必要になるのでしょうか。それは、StrutsがもともとHTTPリクエスト/リスポンスを使ったアプリケーションで解決しようとしていた問題が、リッチインターネットアプリケーションやSOAPのような新たなテクノロジーを利用すれば解消できてしまうからです。例えば、リッチインターネットアプリケーションはクライアントサイドでアプリケーションのステートを管理することができます。そして、SOAPはHTTPプロトコルを利用してリモートオブジェクトのメソッドを呼び出すことを可能にします。
このアプローチその2では、以下を実装します。
- リッチ インターネット アプリケーションとSOAPを最大限に活用する。
- この2つのテクノロジーを利用することで、不要になるStrutsの機能を回避する。
- クライアントアプリケーションと、サービスとして公開されているStrutsコンポーネントとの直接のやり取りを可能にする。
サービスファサード(Service Facade)の使用
Strutsフレームワークでは、コントローラーコンポーネントがHTTPリクエスト/リスポンスの仕組みに大きく依存しているため、Struts APIもまた、リクエスト、リスポンスのオブジェクトに大きく依存することになります。しかし、サービスとして公開したいStrutsオブジェクトの前にサービスファサードを配置すれば、この依存関係を持たないAPIが実装できます。サービスファサードを追加するこの手法は、サービス指向アーキテクチャー(SOA)でよく用いられるデザインパターンです。しかも、既存のStrutsモデルをFlex以外のクライアントへも公開する手段を提供します。
以下の図は、このアプローチを取り入れたユーザー登録アプリケーションのワークフローを図式化したものです。(RegistrationServiceがサービスファサードになります)これで、クライアントとサーバーの連携が、ページとフロントコントローラの連携ではなく、コンポーネントとサービスの連携に変わったことに注目してください。
RegistrationService
クラスのソースコードは下記のようになっています。
package samples.struts; import org.apache.struts.action.*; import java.util.*; public class RegistrationService { public ArrayList register(RegistrationForm form) { // フォーム フィールドの検証 ActionErrors errors = form.validate(null, null); // フォーム フィールドの検証に成功した場合に限り、 // Action を実行 if (errors.isEmpty()) { RegistrationAction action=new RegistrationAction(); errors = action.execute(form); } // クライアント アプリケーションに返す // エラー メッセージ配列の作成 ArrayList messages = new ArrayList(); Iterator i = errors.get(); while (i.hasNext()) { messages.add(((ActionError) i.next()).getKey()); } return messages; } }
ソースコードの解説
register()
メソッドはパラメータとしてRegistrationForm
オブジェクトを使用します。またここでは、クライアントからサーバーへデータを転送するオブジェクトとして、Strutsアプリケーションで使用していたJavaBeansのRegistrationForm
を使用しています。このアプローチのワークフローは以下のとおりです。
- Flexアプリケーションで、
RegistrationForm
オブジェクトに対応するActionScriptのオブジェクトを用意しています。アプリケーションがこのActionScriptオブジェクトにデータを格納します。 - このActionScriptオブジェクトはクライアントサイドでSOAPリクエストに直列化さます。
RegistrationForm
インスタンスへ非直列化されます。 R+egistrationAction
クラスを改良することで、HTTPリクエスト/リスポンスとSOAPによる呼び出しの両方をサポートすることも可能です。
package samples.struts; import javax.servlet.http.*; import org.apache.struts.action.*; public final class RegistrationAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ActionErrors errors = execute(form); if (!errors.isEmpty()) { saveErrors(request,errors); return new ActionForward(mapping.getInput()); } request.setAttribute("registration", form); return mapping.findForward("success"); } public ActionErrors execute(ActionForm form) { ActionErrors errors = new ActionErrors(); RegistrationForm registrationForm = (RegistrationForm) form; String email = registrationForm.getEmail(); // 暫定的なビジネス ロジック: // 通常この部分にビジネス オブジェクトとの処理を配置 if (email == null || email.indexOf("@") < 0) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("errors.registration.failed")); } return errors; } }
既に実装済みのStrutsコンポーネント(またはそのファサード)をサービスとして公開する方法には2つあります。
- コンポーネントをWebサービスとして公開する。
- Flexの
RemoteObject
タグを使用し、Strutsコンポーネントやファサードのメソッドを直接呼び出す。この際、RemoteObject
タグは、リモートのメソッド呼び出しにSOAPを利用します。
どちらの方法を採用するかは、アーキテクチャー上の問題というより、実装上の問題になります。一般的にはWebサービスを使う方がより汎用性のあるアプローチであり、柔軟性に富んでいるといえるでしょう。例えば、Webサービスを使う場合は、FlexアプリケーションがStrutsアプリケーションと同じサーバー上に配置されている必要がありません。一方のRemoteObject
を利用する場合は、公開したいStrutsコンポーネントをWebサービスとして実装する手間が省けますが、FlexとStrutsアプリケーションが同一マシン上に配置されている必要があります。
以下のソースコードはWebサービスとしての実装例です。ここでは、Flexのクライアント アプリケーションが、Webサービスとして公開されたRegistrationService
を呼び出しています。
<?xml version="1.0" encoding="iso-8859-1"?> <mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" height="600"> <mx:WebService id="registrationWS" wsdl="http://localhost:8101/royale/services/Registration?wsdl" result="alert(registrationWS.register.result==null?' Thank You!':'Sorry, your registration failed')"> <mx:operation name="register"> <mx:request> <form>{registrationModel}</form> </mx:request> </mx:operation> </mx:WebService> <mx:Model id="registrationModel"> <firstName>{firstName.text}</firstName> <lastName>{lastName.text}</lastName> <phone>{phone.text}</phone> <email>{email.text}</email> </mx:Model> <mx:Form> <mx:FormItem label="First Name" required="true"> <mx:TextInput id="firstName" width="200"/> </mx:FormItem> <mx:FormItem label="Last Name" required="true"> <mx:TextInput id="lastName" width="200"/> </mx:FormItem> <mx:FormItem label="Phone" required="true"> <mx:TextInput id="phone" width="200"/> </mx:FormItem> <mx:FormItem label="Email" required="true"> <mx:TextInput id="email" width="200"/> </mx:FormItem> <mx:FormItem> <mx:Button label="Register" click="registrationWS.register.send()"/> </mx:FormItem> </mx:Form> <mx:Repeater id="errorList" dataProvider="{registrationWS.register.result.item}"> <mx:Label>{errorList.currentItem}</mx:Label> </mx:Repeater> </mx:Application>
ソースコードの解説
<mx:Model>
タグがクライアントアプリケーションの処理するデータを表しています。<mx:Model>
タグの中で宣言されている、Data
要素はそれぞれ、データ入力用のコントロールにバインドされています。エンドユーザーがユーザーインターフェイスになんらかの変更を加えると、<mx:Model>
タグが自動的にこの変化を処理します。- Webサービスの
register
メソッドの入力パラメータは、registrationModel
にバインドされています。このメソッドの呼び出しがかかると、自動的にregistrationModel
オブジェクトが引数として渡されます。
registrationModel
オブジェクトはActionScriptのオブジェクトとして実装されています。このオブジェクトのプロパティが、サーバーサイドにあるRegistrationForm
JavaBeansの同名のプロパティに呼応しているので、Webサービスとして公開されているRegistrationService
のregister()
メソッドの引数として、registrationModel
オブジェクトを使うことができるのです。Flexは、registrationModel
オブジェクトをクライアントサイドでSOAPリクエストに直列化し、このSOAPパケットをサーバーサイドでJavaBeansのRegistrationForm
インスタンスへ非直列化します。
もう1つの実装方法
<mx:Model>
タグを使ってクライアントサイドのデータを表す代わりに、ActionScriptのオブジェクトをカスタム作成する方法もあります。例えば、下記のコードにあるような「RegistrationForm.asクラス」を実装します。
class RegistrationForm { var firstName:String; var lastName:String; var phone:String; var email:String; function RegistrationForm(firstName:String, lastName:String, phone:String, email:String) { this.firstName=firstName; this.lastName=lastName; this.phone=phone; this.email=email; } }
このクラスを<mx:Model>
タグの代わりに使用して、クライアントサイドでのユーザー登録データをカプセル化することができます。
<?xml version="1.0" encoding="iso-8859-1"?> <mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" height="600"> <mx:Script> var form; function register() { form = new RegistrationForm(firstName.text, lastName.text, phone.text, email.text); registrationWS.register.send(); } </mx:Script> <mx:WebService id="registrationWS" wsdl="http://localhost:8101/royale/services/Registration?wsdl" result="alert(registrationWS.register.result==null?' Thank You!':'Sorry, your registration failed')"> <mx:operation name="register"> <mx:request> <form>{form}</form> </mx:request> </mx:operation> </mx:WebService> <mx:Form> <mx:FormItem label="First Name" required="true"> <mx:TextInput id="firstName" width="200"/> </mx:FormItem> <mx:FormItem label="Last Name" required="true"> <mx:TextInput id="lastName" width="200"/> </mx:FormItem> <mx:FormItem label="Phone" required="true"> <mx:TextInput id="phone" width="200"/> </mx:FormItem> <mx:FormItem label="Email" required="true"> <mx:TextInput id="email" width="200"/> </mx:FormItem> <mx:FormItem> <mx:Button label="Register" click="register()"/> </mx:FormItem> </mx:Form> <mx:Repeater id="errorList" dataProvider="{registrationWS.register.result.item}"> <mx:Label>{errorList.currentItem}</mx:Label> </mx:Repeater> </mx:Application>
最後に
JavaベースのWebアプリケーションを開発する開発にStrutsは数々の支援機能を提供します。しかし、エンドユーザーの体験を改良するという点からは、Strutsにメリットがありません。リッチインターネットアプリケーションの開発プラットフォームであるMacromedia Flexを利用すれば、Strutsを使った効率的な開発を実現しながら、ユーザー体験を向上させることが可能です。FlexとStrutsはどちらもMVCアーキテクチャーをベースとする開発プラットフォームでありながら、MVCの別々の部分に重点を置くフレームワークです。したがって、これらのテクノロジーを組み合わせることは当然のことといえるでしょう。なお、FlexとStrutsの連携には、HTTPリクエスト/リスポンスを利用して従来のStrutsのワークフローを最大限に生かす方法と、Strutsのコンポーネントをサービスとして公開し、サービス指向アーキテクチャー(SOA)として統合する、いずれの手法も利用可能です。