ObectSquare [2010 年 10 月号]

[技術講座]


Spring Rooのご紹介

株式会社オージス総研 技術部クラウドインテグレーションセンター
鵜野 和也

1. はじめに

IT業界全体をクラウドコンピューティングの盛り上がりが席巻しています。最近のJava開発におけるクラウド関連の動きとして、 SpringSourceがSalesforce.com及びGoogleとの連携強化を発表し、その存在感を増してきています。 クラウドの特徴である迅速性という点において、SpringはJBoss等のJavaEE陣営に対し仕様(標準ではなくデファクトスタンダード)、 実装(JavaEEコンテナではなくてTomcatベースのtcServer)の両面でクラウドとの相性に優れるのではないかと感じています。 今回は、SpringSourceのパブリッククラウドベンダーへの対応の中でも中核技術の一つとして位置づけられている、 Spring Roo(以下、Roo)について紹介し、サンプルアプリを生成、生成されたアプリのソースコードについて簡単な解説を したいと思います。

2. Spring Rooとは何か

Rooが何であるか?一言で簡潔に述べると、「拡張の容易なテキストベースのRADツール」であると言えます。 Rooを利用する開発者の視点で表現すると「誰かが肩越しにのぞいていて、あなたの仕事の一部、特に DRY的な部分を肩代わりしてくれるツール」であるとも言えるでしょう。Rooの特徴的な部分を少し見てみましょう。

ランタイムではない
Rooはランタイムではありません。実行時にクラスパスに含めるべきjarは存在しません。 そのため、以下のような問題は発生しません。
  • 動的プロキシの介在によるパフォーマンス低下
  • ロードすべきクラスの増加によるメモリ消費
  • 必要なjarを含めることによるwarのファイルサイズ増大
コード生成中心
Rooは状況に応じてアクティブ又はパッシブにコード生成を行います。
Rooはコンソール上のシェルとして動作し、コマンドを入力するとJavaのソースコード、Springの設定ファイル、Mavenの pomファイルへの依存関係の追加を自動で行ってくれます。
また、Rooは開発者がエディターあるいはIDEで作業した内容をモニタリングし、それに応じてプロジェクト内の関連 するファイルを自動的に更新してくれます。
Java/Springベースである
開発ツールに対するコマンドの入力でエンティティのCRUD操作を行えるWeb画面が自動生成されるようなツールフレーム は今までにも少なからず存在していました(Rails, Jboss Seam)。これらに対し以下のことが言えるでしょう。
  • Javaベースであるため、Ruby等の動的言語よりも堅牢性に優れ、大規模開発に向く
  • SpringベースであるためJavaEE準拠のものよりも軽快性に優れ、クラウド環境に向く
デファクトスタンダードの採用
Rooで開発されたアプリケーションは既に十分実績のある技術ベースにしています。 (Spring, JPA, JSP, AspectJ, Maven...)。 これはRooを使用しはじめるに当たり新規に習得しなければならない内容が少ないということです。
一貫性の確保
Rooの生成するコードのアーキテクチャに沿って開発を進めることで、プロジェクト内あるいはプロジェクトを跨って の構造や品質の一環性を確保することが容易になります。

Rooの紹介のまとめとしてRooで提供される具体的な機能(の一部)を記述しておきます。詳細はRooのドキュメントを 参照してください。

  • プロジェクトの生成やMaven依存関係管理
  • setter&getter,toStirng()の自動メンテナンス
  • エンティティ、フィールドとCRUD処理の生成
  • JPAのセットアップ
  • JUnitの生成
  • 検索(JPA QL)処理の生成
  • Spring MVCベースのscaffold生成
  • Spring Web Flowのセットアップ
  • Spring Securityのセットアップ
  • ロギングのセットアップ
  • ...

また、Rooはadd-onベースのアーキテクチャで構築されいるため、今後も様々なadd-onが追加 されることが予想されます。もちろん、あなた自身でadd-onを作成することも可能です。

3. Rooによるサンプルアプリの生成

リファレンスドキュメントやネット上の記事ではRooのコマンドラインシェルを使用した解説が多いようなので、 本記事ではSpringSourceToolSuite(以下、STS)上でプロジェクトの作成からWebアプリの雛形の作成、 起動するまでの流れを作業上の留意点を付記しながら簡単に紹介します。

3.1 使用する環境

筆者が使用した環境は以下のとおりです。

  • Sun JDK 1.6.0_15
  • SpringToolSuite 2.3.3M2
  • Spring Roo 1.1.0M2(SpringToolSuiteに同梱)

※執筆時点のSTS/Rooの最新版は2.3.3M3/1.1.0M3です。M2から多数のバグフィックスが行われているため、 M3を使用する場合は、この記事に記述したワークアラウンドの一部は不要になっている可能性があります。

3.2 開発環境のインストール

SpringToolSuite(以下、STS) は SpringSourceのサイト からダウンロードできます。 本記事では、springsource-tool-suite-2.3.3.M2-e3.5.2-win32-installer.exe を使用します。 ダウンロードしたファイルをダブルクリックするとインストーラが起動するので、通常の要領でインストールしてください。
また、Proxy経由でインターネットアクセスをする環境では通常のEclipse, Mavenと同じ要領でそれぞれに、Proxyの設定が必要です。

NOTE
じつは、Roo1.1.0M2には生成されるpom.xmlの内容に問題があります(R00-1111)。 この問題を回避するために事前に https://maven.springframework.org/milestone/org/aspectj/aspectjtools/1.6.10.M1/aspectjtools-1.6.10.M1.jar をダウンロードして以下のコマンドでローカルリポジトリにインストールしておいてください。

$ mvn install:install-file -DgroupId=org.aspectj -DartifactId=aspectjtools -Dversion=1.6.10.M1 -Dpackaging=jar -Dfile=aspectjtools-1.6.10.M1.jar

3.3 生成するアプリケーションについて

今回作成するアプリケーションはRooの Reference Guide の冒頭に登場するten-minutesアプリケーション を例にとって紹介したいと思います。属性が一つしかないエンティティ一つと、そのCRUD画面のみという、いささかシンプルな内容ですが Rooによる開発イメージの雰囲気を感じていただくことはできるかと思います。

3.4 Rooによるサンプルアプリケーション生成の手順

では実際にアプリケーションを作成(生成?)してみましょう。まずは、STSを起動してください。

Rooプロジェクトの作成
STSを起動した後、Package Explorer を右クリックして、"New"->"Roo Project"を選択します。 表示されたダイアログにプロジェクト名(ten-minutes)とトップレベルのパッケージ名(com.tenminutes)を入力して、 "Next"。次の画面で"Finish"をクリックします。
new Roo Project dialog
NOTE
初回のみ以下のAJDTダイアログが表示されます。コンテンツアシスト等に必要な機能なので "Yes"を選んだ方がいいでしょう。"Yes"をクリック後、固まったような状態になりますが、 しばらくすると、リスタートを要求するダイアログが表示されるので、"Yes"を選んでください。
AJDT Project dialog
Roo Shellの起動
STSが再起動されると、インデックスを再構築する旨のダイアログが表示されるので、"Yes" を選んでください。起動が完了すると、Rooプロジェクトが生成されていることを確認できると思います。
STS restarted
この状態ではRoo Shellが起動していませんので、まずRooを起動しましょう。 画面下部の"Roo Shell"ビューから"Open Roo Shell for projects.."をクリックします。 ダイアログが表示されるので、作成したプロジェクトにチェックを入れて"OK"をクリックすると、 Roo Shellが起動します。
roo shell view
コマンドの入力
STS上の"Roo Shell"ビューではビュー下部のテキストボックスにコマンドを投入して、コードや設定ファイルを生成します。 まずは "hint"と入力してみてください。とりあえずJPAの設定をせよとのメッセージが表示されます。 この hint コマンドはプロジェクトの状態によって適切なコメントをしてくれます。
ではJPAの設定です。テキストボックスに"persistence setup" まで入力してCTL+Spaceを 入力してください。指定可能なオプションがリストされます。
roo shell view
ここでは、まず、"--provider"を選択します。 テキストボックスの内容が"persistence setup --provider"となったら、さらにCTL+Space。 今度はproviderに指定可能な定数が表示されるので、HIBERNATEを選択してください。 このようにRooは強力な補完機能で開発を支援してくれます。さらにCTL+Spaceを駆使して、 以下のコマンドを入力してください。JPA関係の様々な設定がこのコマンド一つで完了します!
persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY
NOTE
「コマンド一つで完了します!」のハズなのですが筆者の環境では、以下のようなエラーになりました。

10/08/06 15:21:14 JST: Build errors for ten-minutes; org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal on project ten-minutes: The repository system is offline and the requested artifact is not locally available at C:\Documents and Settings\P9970740\.m2\repository\org\hibernate\hibernate-entitymanager\3.5.1-Final\hibernate-entitymanager-3.5.1-Final.jar

  org.hibernate:hibernate-entitymanager:jar:3.5.1-Final



from the specified remote repositories:

  spring-maven-release (https://maven.springframework.org/release, releases=true, snapshots=true),

  spring-maven-milestone (https://maven.springframework.org/milestone, releases=true, snapshots=true),

  spring-roo-repository (https://spring-roo-repository.springsource.org/release, releases=true, snapshots=true),

  JBoss Repo (https://repository.jboss.org/nexus/content/repositories/releases, releases=true, snapshots=true),

  central (https://repo1.maven.org/maven2, releases=true, snapshots=false)

Path to dependency: 

  1) com.tenminutes:ten-minutes:pom:pom:0.1.0.BUILD-SNAPSHOT

  2) org.hibernate:hibernate-entitymanager:jar:3.5.1-Final

この例外が発生した場合はten-minutes/pom.xmlの33行目を以下のように編集して、保存してください。


修正前 https://repository.jboss.org/nexus/content/repositories/releases

修正後 https://repository.jboss.org/maven2

エンティティの作成
ではエンティティクラスを作ってみましょう。以下のコマンドを入力してください。
entity --class ~.Timer --testAutomatically
context aware prompt
ここで、コマンドの実行前後でプロンプトの表示が"roo>"から"~.Timer roo>"に変わっています。
(Rooでは"~"はプロジェクト作成時に決定したトップレベルパッケージの省略形という意味になります。) Rooはコマンド実行のコンテキストを把握しており、このプロンプトは対象となるクラスを指定せずにコマンドを実行すると、 このクラス(Timer)に対するオペレーションと解釈することを示しています。 では、エンティティにフィールドを足しましょう。
field string --fieldName message --notNull
これで、JPAのエンティティができました。
CRUDの作成
今度は、先程のTimerエンティティに対応するCRUD画面を生成します。以下のコマンドを入力してみてください。
controller all --package ~.web
この段階で、一行のコードも記述することなく起動可能なWebアプリケーションが生成されました! では、起動してみましょう。
NOTE
このコマンド実行後にurlrewrite.xmlにエラーマーカーが付きますが、とりあえず気にせずに進めてください。 安定版がリリースされる頃には解消していることでしょう。

3.3 サンプルアプリの起動

では、起動してみましょう。プロジェクトを右クリックして"Run as"=>"Run on Server"を クリックしてください。"Run On Server"ダイアログが表示されるので、"Finish"をクリックしてください。
最初の起動時にはSpirng Insightのメトリクス収集を有効にする旨のダイアログが 表示されるので、"Yes"としてください。(Spring Insightに関しては後述します)

insight dialog
NOTE
起動する際にも、おまじないが必要です。 プロジェクトを右クリックして"Properties"=>"Java EE ModuleDependency"を参照し、 "Maven Dependencies"にチェックを入れてOKをクリックしてください。
NOTE
起動時に例外が出た場合は、プロジェクトをクリーンして再度試してみてください。
筆者が見たSpringSourceのWebinarでもデモの起動時に例外が発生し、何食わぬ風でクリーンしてました。

起動が完了したら、https://localhost:8080/ten-minutes/にアクセスしてください。 まったくコーディングなしで、動作するWebアプリが完成しました。

ten-minutes webapp

3.4 アプリケーションの動作状況の確認(Spring Insight)

せっかくアプリを起動したので、Spring Insightについても少しだけ触れておきます。 https://localhost:8080/insight/にアクセスしてみてください。以下のような画面が表示されます。

insight

Spring InsightはSTSに同梱されているtc Server Developer editionに組み込まれた アプリケーションの挙動とパフォーマンスを確認するためのツールです。
開発者はSpring Insightが提供する情報から自身の記述したコードがどのように振舞ったか、 どのような性能であったかを素早く把握することが可能です。
操作方法等は細かく記述しませんが、アプリケーションを少し動かした後で、画面上のタブやらグラフやらをクリック してみてください。Spring Insightが粒度の粗いサマリの情報から、より詳細な情報へナビゲートしてくれます。 送受信したHTTPのヘッダから、JDBCで発行したSQLのバインド変数まで、単一のUIで様々な情報を参照する ことが可能です。
筆者としては、HibernateのSQLログでバインド変数が"?"になっていたり、HTTPのヘッダを 確認するためにブラウザにプラグインを入れてみたり、と別に困りはしないけれども痒いところに手が届きづらい思いを した記憶があるので、このように単一のUIで上から下まで情報が参照できるのはうれしい機能です。

・HTTPヘッダの内容を参照

http_headers

・SQLとバインド変数の内容を参照

jdbc_query

4. Rooが生成したサンプルアプリの構造

ここからはRooが生成したアプリケーションがどのような構造になっているか、簡単に見ていきたいと思います。

4.1 全体の構成

全体のレイヤ構成は以下のようになっています。

  • JPAによるActive Record形式のEntity層
  • Spring MVCによるREST形式のWeb層

このようにシンプルな二層構成になっており、開発者自身がWeb層のControllerを追加する場合も、この構成に従う ことが推奨されています。 Entity層とWeb層の間に入るサービス層はオプショナルの位置づけになっており、以下のような状況での使用が想定されます。

  • 複数のエンティティに跨る処理であり、どちらのエンティティにもフィットしない。
  • メソッドを公開してリモートアクセス可能とする場合

一つ目の例は、Domain Driven DesignにおけるServiceの定義そのままという感じですね。 また、Reference Guideには、さらに詳しく記述されてるので参照してみてください。

4.2 Entity層 エンティティとその永続化

エンティティのコードを見てみましょう。まずはcom.tenminutesパッケージを見てください。 Timerクラスのみが存在しており、中身は以下のようになっています。

timer_class

@Roo*というアノテーションが複数付いていますが、messageフィールドの他はなにも定義されていないように見えます。 じつはRooの生成するJavaコードはAspectJのITD(inter-type declaration)という技術で別ファイルで管理 されています。
Package Explolerの右上の▽からFiltersを選択し"Hide generated Spring Roo ITDs"のチェックをはずして ください。以下のようなTimer_Roo_*.ajというファイルが確認できます。

aj files

こられはAspectJ ITDのファイルであり、 コンパイル時にTimer.javaの内容に加えてTimer_Roo_*.aj を呼び出す処理が書き込まれたTimer.classが生成されることになります。 それぞれの役割は以下のようになっています。

*_Roo_Configuarable.aj Springの@Configuarableを付与します。
*_Roo_Entity.aj ActiveRecordを実現するためのJPA関連のコードです。
*_Roo_JavaBean.aj setterとgetterです。
*_Roo_ToStiring.aj これは、想像つきますね。:-)

NOTE
ITDファイル(*.aj)はRooの管理下にあり、開発者が編集するべきではありません。
では、Rooが生成した処理とは異なる自前のtoString()を実装したい場合はどうしたらよいでしょう?
この場合、Rooに構わずTimer.javaファイルにtoString()を定義して保存してください。 Rooはその修正を検出して、自動的にTimer_Roo_ToString.ajを削除してくれます。 これが冒頭で述べたアクティブなコード生成機能になります。 ITDファイルに定義された内容を.javaクラスに取り込む際は、リファクタリング機能の"Push In"をオススメします。
@Configurable
もっともなじみのないのが、Roo_Configurable.ajに定義されている@Configurableではないでしょうか。 簡単に説明すると、通常のDIはDIコンテナの管理下にあるインスタンス同士でインジェクション が可能ですが、この@Configurableアノテーションをつけると、どのようにインスタンス化されたかに関係なくインジェクションが可能になります。 つまり、new演算子を使用してインスタンス化したオブジェクトにもDIを適用することができます。 この特性はTimer_Roo_Entity.ajでも使用されているので、興味があれば見てみてください。
ActiveRecordによる永続化
Rooの生成するアプリケーションにはDAOは存在せず、ActiveRecordパターンに従った永続化ロジックが生成されます。 これらは*_Roo_Entity.ajに記述されており、以下のような操作が提供されることになります。
persist() 基本的にはJPAのEntityManagerに処理を委譲します。
merge() 基本的にはJPAのEntityManagerに処理を委譲します。
remove() 基本的にはJPAのEntityManagerに処理を委譲します。
flush() 基本的にはJPAのEntityManagerに処理を委譲します。
findエンティティ名(Long id) 指定されたidを持つエンティティを返します。
findエンティティ名Entries(int, int) 全てのエンティティから範囲を絞って(何件目から何件目まで)返します。
findAllエンティティ名() 全てのエンティティを返します。
countエンティティ名() エンティティの数を返します。
persist(),merge(),remove(),flush()には@Transactionalが付与されているので、 基本的はこれらのメソッドの呼び出し前後がトランザクションの単位となります。 ということは、ビジネスロジックを作成する際に、
EntityA a1 = new EntityA();
EntityA a2 = new EntityA();
a1.persist();
a2.persist();
うかつに、このようなコードを書くとトランザクションが二つに分かれてしまいます。自前でSpring MVCのControler あるいはServiceクラスを記述した場合には@Transactionalを適切に付与する必要があります。

DAOについても記述しておきましょう。RooではJava開発で長らくおなじみだったDAO層が消えています。DAO存在した大きな理由 として以下があげられます。
  • Entityが永続化メカニズムに依存しないようにするため。
  • 永続化メカニズムをスイッチできるようにするため。
しかし、Rooではエンティティと永続化メカニズムの分離はITDによって実現されています。
そして、永続化メカニズムのスイッチに関してはJPAがJavaの永続化メカニズムの標準として既に大きな支持を獲得している現状を 考えれば、JPAの実装をスイッチで対応するというのが現実的な方法だと考えます。 また、JPAはManaged状態のエンティティの属性変化を検出してUPDATE文を発行する 動きをするため、仮にDAO層を作成しJPA依存のコードをそこに集中させたとしても、DAO層の入れ替えで永続化メカニズムをスイッチ できるわけではありません。

4.3 Web層 コントローラとビュー

コントローラの構造
Controllerもロジックのほとんどは*_Roo_Controller.ajに記述されています。 生成されたメソッドとTimerクラスを例にしたURLとのマッピングは以下のようになります。 RooのWeb層がREST形式で生成されていることを把握してもらえるかと思います。

create POST /timers Timerエンティティの保存
createForm GET /timers?form 新規入力用フォームの表示
show GET /timers/(id) Timerエンティティの表示
list GET /timers?page=m&size=n Timerエンティティ一覧の表示
update PUT /timers Timerエンティティの更新
updateForm GET /timers/(id)?form 更新用フォームの表示
delete DELETE /timers/(id) エンティティの削除

ビューの構造
RooはJSPベースのビューを生成します。生成されるフォルダの構造は以下のようになっています。
/styles スタイルシート
/images 画像
/WEB-INF/classes/*.properties テーマの設定
/WEB-INF/config/*.xml Web関連のSpring設定ファイル
/WEB-INF/i18n/*.properties 国際化メッセージプロパティ
/WEB-INF/layout/layout.jspx Tilesのマスターレイアウト
/WEB-INF/tags/*.tagx XML形式で記述されたカスタムタグ
/WEB-INF/viwes/**/* Tilesで使用する各JSP(jspx)の定義

Rooが生成したControllerに対応するJSPビューはRooの管理下にあります。そして、Controllerに対応付けられた エンティティにフィールドが追加されると自動的にJSPをメンテしてくれます。そのため、Rooが生成したビューを開発者が編集 する場合にも一定のルールがあります。以下はRooが生成したmenu.jspxのソースです。
menu
<menu:category>、<menu:item>等見慣れないタグがあります。これらのタグは/WEB-INF/tagsフォルダに配置された タグライブラリです。
/WEB-INF/tags配下のタグライブラリ群はJavaのソースコードを持たず、XMLのみで定義されており、開発者がカスタマイズしてビューの見た目 を変更することが可能です。
menu.jspxのタグにはz="nZaf43BjUg1iM0v70HJVEsXDopc="のような属性が付いています。Rooはこのz属性を使用して開発者がタグの内容を更新 したか否かをチェックしています。開発者がタグを更新したことを検知すると、z属性の値は"user-managed"となります。 一度、"user_managed"となったタグを再びRooの管理下に戻したい場合は、z属性の値を"?"にしてください。Rooは"?"を検出すると、 z属性の値を再計算します。
NOTE
筆者の環境では、このz属性の更新がRooを再起動するまで検出されなかったりと、なかなかドキュメントに 記述されたとおりに動作しませんでした。この辺りは今後開発が進めば解消されると思われます。
OpenEntityManagerInViewFilter
RooはEntityManagerをOpenEntityManagerInViewFilterで管理しています。リクエストの受けた段階でEntityManagerを生成し、 レスポンスを出力し終わったタイミングで、EntityManagerをクローズします(ここで話をしているのはPersistenceContextのスコープ です。トランザクションは上記のとおり@Transactionalでコントロールされます)。EntityManagerのクローズをレスポンス生成後まで、遅延 することにより、JPAでのLazy Loadに対応しているわけです。
NOTE
この辺りは、JBoss Seamとの思想の違いが見受けられるところです。JBoss Seamでは、
  • リクエストを受けてからビジネスロジックの実行が終わるまで
  • ビューのレンダリング開始から終了まで
という具合に単一のHTTPリクエストの処理に二つのトランザクションを使用していました。ちなみに、その理由は以下だということでした。
  • レンダリング時にトランザクションがかかっていない場合 lazy load時にauto-commitモードで動作することになり、lazy loadが発生する 度にトランザクションのオープン&クローズが発生し負荷が高い
  • lazy load毎に別トランザクションであり結果の一貫性が保障できない。(これはアイソレーションレベルに依存する話だと思うのですが)
興味のある人はパフォーマンスを比較してみても面白いかもしれません。(JSPとJSFの差の影響度の方が大きいような気もしますが。)

5. 最後に

ここまで、Spring Rooについて簡単にご紹介してきましたが、どのような印象を持たれたでしょう?
記事の中でもいくつかNOTEとして、いくつか不具合めいたことを記述しましたが、筆者が経験した事象では、

  • ITDを使用しているせいかエディタ上での入力補完候補が表示されない
  • *.ajにブレークポイントを設定できない

等など、本格的に利用すると気になることはまだまだ出てきそうな印象でした。しかし、これらの問題は開発 が進むにつれて解消されていくでしょう。少し前の話ですがVMwareとSpringSourceはSalesforce やGoogleとクラウド関連技術での連携を強化しました。ここで、Spring RooはSpringSourceToolSuiteや tcServerと共に中核技術として位置づけられています。現在のクラウド技術への盛り上がりがRooの成長を後押し してくれることでしょう。
また、現時点でフルにRooを使用しなくても、

  • 最初の環境のセットアップのみをRooにやらせて、後を引き継ぐ
  • SpringベースとしたWebアプリケーションの学習用として使用する

など、利用方法はいろいろとあるかと思います。開発の進行状況を見守りつつ、おいしく使えるところは利用する。 そんな使い方でも良いのではないでしょうか?
では、Enjoy Spring Roo!



© 2010 Uno Kazuya
HOME HOME TOP オブジェクトの広場 TOP