Cedric Beustはアスペクト指向プログラミング(AOP)を「これからも少数の開発専門家の特権のままとなるすばらしい構想」(source)と称しました。SpringとJBossがあっても、一部の人にとって、手をつけるにあたっての障害は大きいままです。幸運なことに、これは動的言語が役に立つ場面かもしれません。動的言語は、AspectJの実践に先立って実験と学習のためのわかりやすい導入部となるほか、それ自身きわめて生産性の高い環境を提供します。Java開発者はそれほどなじみの場所から離れる必要すらありません。Javaに似たシンタックスを持つJVM動的言語のGroovyは、AOPの模倣を容易にする見事なほど強力な機能を誇ります。今回の記事はGroovyに焦点を合わせることになりますが、最近の動向を見ると、かなり愛されながら恐れられてもいるRubyとの比較が求められています。Ruby信奉者は20%の努力で機能の80%を獲得できます。
AOPにより、多数のメソッド、クラスがある中でもつれ合った状態が続くかもしれないコードをモジュール化できます。例えば、多数の作業単位のあたりで(around)同じセキュリティ確認をしていることに気づき、他の単位より前に(before)いくつかの変数のインスタンスを生成し、他の単位が完了または例外(exception)を投入したあとで(after)情報をログするかもしれません。AOPにより、DRY(重複排除)原則に違反して複数回この機能を記述してしまった場合に、この機能すべてを一旦記述しておき、適切な時点で(何かの前、後、その周り、または例外が投入されたとき)メソッドにこれを適用させることができます。通常Javaでは、2つのメカニズムのうち1つを使います。この機能を直接挿入するため私たち自身のクラスのバイトコードを修正するのにフレームワークを利用するか、自身のクラス向けにプロキシを動的に生成するかします(JDKダイナミックプロキシかCglibを使用)。Groovyでは、これから見ていきますが、どちらの技法も必要ありません。
MOP:単にクリーニングのためではない
Groovyのメタオブジェクトプロトコル(MOP)(英語・サイト)は、アプリケーション開発者が言語の実装とふるまいを変更できるメカニズムです。GroovyのMOPはとりわけ強力です。慎重な設計の結果として、すべての動的なメソッドコールはinvokeMethodを介して経路が決められ、getPropertyとsetPropertyを介してプロパティにアクセスします。したがって、私たちが生成するObjectの中核的なふるまいを修正するための窓口は一箇所しかないのです。invokeMethod、getProperty、setPropertyを無効にすることにより、プロキシまたはバイトコード操作の必要なく、Objectへの1つ1つのコールをさえぎることができます。
関数を解き放つ
「この地に戻り、我々の命は奪えても自由は決して奪えないと敵に告げるただ一度の機会を得るために、今日からのすべての日々を差し出す気はあるか?」と、Mel Gibsonは映画「ブレイブハート」の中でスコットランド人をこのように鼓舞しています。
言語の熱烈な愛好者であるSteve Yeggeは、ユーモアを交えてJavaを「名詞の王国」(source)と称しています。各関数がとらわれの状態になっており、名詞に付随してしか移動できない国という意味です。GroovyはObjectに縛られ服従を強いられる圧制から関数を解き放ちます。Groovyのクロージャは囲んでいる範囲にアクセスできる無名関数であり、自由に繰り返し呼び出すことが可能で、データのように順に回すことができます。構文的にクロージャはJavaコードブロックのように見えるものの中で定義されます。下記の例は"hello world from Groovy!"を印刷します。
String printMe = "hello world"
def c = { parameter | println(printMe + parameter} }
c(" from Groovy!")
Groovy MOPの持つパワーと組み合わせたこのような軽量の関数を生成する能力により、ほんの少しのコードでAOPスタイルの魔法をかけることができます。
始める前に
この下にある各例に従いたい場合、まずは次の手順に従う必要があります。
- Groovyのダウンロード
GroovyはGroovyサイト(英語)からダウンロードできます。 - Groovyインストールページ(英語)にあるインストールの指示に従ってください。
- 任意でGroovy Eclipseプラグインをダウンロードし、インストールできます。プラグインの更新サイトはhttp://dist.codehaus.org/groovy/distributions/update/(英語)です。若干の追加情報を載せたWikiサイトがここ(source)にあります。Groovy Eclipseプラグインはまだリリース前ですが、少しいじるだけで、うまく機能すると思います。次のことを考慮する必要があるかもしれません。
-
ステップフィルタを設定する:preferences/java/debug/stepfilteringにおいて、次のクラスに関してステップフィルタリングをオンにします。
Groovyは見えないところで多くの作業をこなしますが、これによりコードを実行する際にEclipseプラグインがGroovyインフラに誘導するのを防ぎます。
- クロージャを超えてではなくクロージャの中に入ります。
- 現在はdebug as/groovyを直接使えません。代わりに、新しいDebug Application構成を作成しなければなりません。この手順はwiki(サイト・英語)で定義されています。
- プラグインにソースを見つけさせるようにするには、Eclipseにさらにいくつかの手がかりを与える必要があるかもしれません。方法として、クラスディレクトリのコピーを作成し、それを最後のクラスフォルダとして追加するやり方があります。次にproject/properties/librariesで、ソースフォルダをダミーのクラスフォルダに加えることができます。
-
イテレーション1:はじめに
AOPにより、特定のメソッドが呼び出されたときに任意の関数を実行することができます。これを行うとき、「アドバイス」を適用していると言われます。beforeとafterというアドバイスタイプはとくにGroovyでは実行が容易です。
インフラを定義する
まずちょうど遮られたばかりのメソッドコールの詳細を保持できるGroovyクラスを記述してみましょう。このObjectをパラメータとしてアドバイス実装のすべてにまわします。これにより、コンテキストを「理解」させ、ターゲットとするObjectに対するプロパティおよび入ってくるパラメータへのアクセスと変更すら行えます。
注:ここでインスタンス変数の修飾子として使われる"def"というキーワードは、変数がプロパティであることを示しています。Groovyは私たちのために標準的なJavaBeanのGetterとSetterを追加します。また後で重要となるため、このクラスがExpandoを拡張することにも留意してください。
invokeMethodを無効にし、任意のコールをbeforeとafterのアドバイスタイプに挿入するクラスを定義できます。AOPに似た機能を活用するには、このClassから継承するだけでいいのです。クラスへのメソッドコールそれぞれに関してGroovyに強制的にinvokeMethodを呼び出させるには、GroovyInterceptableというマーカーインターフェースを実装する必要があります。
このクラスでは、defというキーワードがローカル変数の修飾子として使われています。これは、変数が動的に型付けされていることを示します。またAopDetails Objectのインスタンスを生成するため、Groovyの便利なパラメータ命名サポートも利用します。
この原則は、プロパティアクセスにまで拡張できますが、明確にするために、この実行は皆さんの想像力に委ねておきたいと思います。
始めるための必要なインフラをすべて定義し終えたので、AOPが作動する様子を例とともに示していきます。
作動中の例
Exampleクラスにアドバイスを適用し、有益なステータスメッセージを印刷するためのmainメソッドが必要です。
Groovy Eclipseプラグインを使って順を追っている場合、mainメソッドの開始時点でブレークポイントし、コードを通しで進んでいくことができます。ファイル上で右クリックし、ポップアップメニューからrun-as -> groovyを選択することで、Runner.groovyを実行できます。デバッグするには上記(source)およびwiki(source)の手順に従ってください。実行中のRunner.groovyからのアウトプットは次のとおりです。
Calling print message with no AOP :-
Calling print message with AOP before advice to set message :-
hello world
Calling print message with AOP after advice to output status :-
hello world
Status:message displayed
Calling print message via invokeMethod on a Statically defined Object :-
hello world via invokeMethod
最初に、“message”というキーのもと、空のStringを含むMapと合わせてprintMessageを呼び出します。しかしprintMessageに対する次のコールの前にメッセージを”hello world”に変更するようbeforeアドバイスを宣言し、printMessageに適用させます。次に、ステータスメッセージを出力するためafterアドバイスを適用します。invokeMethodにおいてブレークポイントを設定し、何が起きているか明確に確認することができます。
“invokeMethod”は、beforeとafterマップにおいて現行のメソッド名で実行するためクロージャを確認します。見つけた場合、適切な時点で実行します。
このちょっとした例は、より複雑な実世界の状況に容易に適用できる原則を示します。
イテレーション2:aroundを使う
これまでは単純でしたが、次の反復ではaroundアドバイスを加えます。aroundアドバイスは、アプリケーションのフローを修正できるという点で、より強力なアドバイスタイプです。ターゲットとするメソッドが実行されるかどうかを決める力を持っています。そのようなものとして、セキュリティ機能を実装する上で優れた場所となっています。
インフラを定義する
新しいアドバイスタイプに対処するためにインフラを修正する必要があります。AopDetailsを変更する必要はありません。AopDetailsを設計する際、ベースクラスとしてExpandoを使いました。Expandoは、拡張可能なObjectを提供するため、同様のMOP策略を使う特殊なGroovy Objectです。proceed()メソッドをAopDetailsに追加して、aroundアドバイスがターゲットとするメソッドを呼び出せるようにする必要がありますが、Expandoがあればこれは問題ありません。それでは新しいAopObjectを見てみましょう。
aroundアドバイスクロージャを保持するためマップを追加しました。continueWithというクロージャコールがターゲットメソッドを呼び出しており、一時的にそれをAsopDetails Objectにproceedメソッドとして追加します。適したaroundアドバイスが存在すればそれを呼び出します。そうでなければターゲットメソッドに直接続きます。
作動中の別の例
今回は事例をもう少し実世界に近いものにしましょう。Webコントローラを模倣します。
Webコントローラは“save”メソッドを実行し、強力なGroovy機能(Expandoとクロージャ)を使ってモデルObjectを作りました。ここで、リクエストからパラメータを読み込むparamsとモデルを保存するsaveという2つの関数をモデルに追加してあります。保存するという動作が慎重を期する更新を実行する場合があるため、何らかのセキュリティ機能をつけるのは賢明かもしれません。
RunnerクラスはMapsを使ってRequestとSession Objectを模倣します。最初は何のセキュリティ機能もなくコントローラに保存しますが、それに続くコールに関しては、セッションに保存されたユーザーIDを確認するaroundアドバイスを挿入します。最後に、ユーザーIDが正しければ本来のメソッドが続行できることを明らかにします。アウトプットは次のとおりです。
Attempting initial save : no AOP security:-
Saved model 10 successfully
Attempting save with AOP security & incorrect id:-
Sorry you do not have permission to save this model (10)
Attempting save with AOP security & correct id:-
Saved model 10 successfully
このスクリーンショットはRunner.groovyにおけるaroundセキュリティクロージャ内のブレークポイントにおけるデバッガを示しています。
イテレーション3:再利用可能なメソッド
クロージャがあれば、メソッドをClassとは別に定義することができます。MOPの場合、Expandoで見てきたとおり、これらの自由なメソッドを複数のクラスに挿入することができます。さらに、異なるメソッドを同じクラスの別のObjectに注入することも可能なのです。このレベルのモジュラー性は、標準的なJavaと比べるとはるかに粒度が高いです。Java開発者である私たちの大部分が、Object Interface実装経由でサービスを注入することに慣れており、通常はXMLを多く使ってこれを達成しているのです。
インフラを定義する
AOPに似たインフラクラスを拡張して、メソッドの挿入とObjectからの削除を可能にすることができます。これを行うため継承階層のいずれかの端において2つの新しいClassが必要です。新しいメソッドをObjectに導入できるAopIntroductionsがベースクラスとなります(AopObjectがAopIntroductionsを拡張するはずです)。特定のメソッドへのコールが実行されないようにするAopRemovalsは、新しいトップレベルのクラスです。
それでは始めましょう
やっと実際に楽しめる段階に来ました。インスタンスメソッドを再利用可能なコンポーネントとして活用できるのです。総称的なメソッドを一箇所において定義し、Objectごとに慎重を期するメソッドへのアクセスを制限しながら、それらを複数のクラスタイプにまたがってObjectに混ぜることができます。
saveメソッドを除外することにより、前回の例からコントローラをリファクタリングしましょう。crudメソッド向けのホルダークラスとなるCrudOperationsクラスを生成します。次にメソッドをCrudOperationsクラスからWebコントローラに注入します。
このコールをCrudOperationsMixinのコンストラクタに通知します。ここで魔法が起きます。
上記のクラスでは、メソッドレファレンスのMapを定義しました。これらレファレンス1つ1つをキーによりホストObject(Webコントローラ)に注入しています。
素晴らしいのは、新しく追加したメソッドに対してもaroud、before、afterのアドバイスタイプを適用できる点です。前回の例からのRunner.groovyはそのまま動きます。しかし新しいクラスにおいてランタイム時間にsaveメソッドを無効にすることで、ひとひねり加えます。
Groovyは演算子のオーバーロードをサポートし、Listクラスは左シフト演算子(“<<”)を使い、新しいObjectをListに追加します。
実行中のRunner.groovyからのアウトプットは次のとおりです。
Attempting initial save : no AOP security:-
save operation
Saved model 10 successfully
Attempting save with AOP security & incorrect id:-
Sorry you do not have permission to save this model (10)
Attempting save with AOP security & correct id:-
save operation
Saved model 10 successfully
Attempting save with save method removed (perhaps for security reasons):-
Caught NoSuchMethodException
下記のデバッガのスクリーンショットでは、saveへのコールがなされた場所であるWebコントローラからsaveメソッドが実際常駐するmixinsクラスへのパスを示すStackTraceを強調表示しました。
GroovyからRubyへ:機関士にとっての代替手段
Groovyが趣味に合わないという方の場合、好きな動的言語で上記の例を再実装することが可能です。例えばRubyでは、invokeMethodではなくmethod_addedフックを無効にすることが可能です。“method_added”は、新しいメソッドがクラスに追加されたときに呼び出されます。メソッドがObjectに追加された場合、alias_methodを介してbefore、after、aroundアドバイスを挿入する実装のため交換し、プロキシすることができます。かつてすべてのWeb開発者を悩ませたJavascriptですら、AOPが容易に実装できる強力なイディオムを備えています。AspectJ(サイト・英語)というそれ用のフレームワークも存在するのです。
まとめ
Groovyのような動的言語はカスタムAOPインフラの実装を容易にします。バイトコードを修正する必要がなく、インフラコードベースは、新しく来た開発者が容易に理解できるよう、小さく保てます。Groovyのメタオブジェクトプロトコルにより、手近の問題に合うようObjectのふるまいを形作ることができ、一級の関数と動的な型付けは動的な機能の挿入を比較的容易にします。ここで説明された事例は完全なAOP機能に向けての第一歩です。まだ実装されていないのはexceptionアドバイス、1つのメソッドに複数のアドバイスを適用するためのサポート(aroundに関するアドバイス連鎖を含む)、アスペクトをObjectに適用する集中型サポートです。しかし、Groovy言語の持つ力のおかげで、比較的わずかな労力でかなり多くのことが達成できるということを実証できたと考えています。
リソース
- 今回の記事で使われたコード(zip)を取得する
- Groovy(サイト・英語)を入手する
- Groovy Eclipseプラグイン(サイト・英語)
- JavaにおけるAOP入門(source)
- Groovy入門(かなり古い)(source)
- Groovyにおけるクロージャ(source)
- GroovyのMOP(source)に関する詳細
- Nothing new under the sun, :around, :before and :after in CLOS (PDF)1988.
著者について
John McCleanはアイルランド・ダブリン在住のソフトウェアエンジニアで、アイルランドのAOL Technologiesに所属しています。Johnは余暇を利用して、Webベースのアプリケーション開発のための生産性の高い環境であるSlingshot Application Framework(source)の開発に携わっています。Johnは関心を持った技術テーマについての見解を載せたブログ(source)を持っています。
原文はこちらです:http://www.infoq.com/articles/aop-with-groovy
(このArticleは2006年10月2日にリリースされました)