アスペクト指向プログラミング
依存性注入をさらに便利なものにするために(訳注:?)、Guiceはメソッドインターセプションをサポートしている。 この機能はあるメソッドが呼び出されるたびに実行される。 これは、クロスカッティングコンサーン(アスペクト)、つまりトランザクション、セキュリティ、ログインなどを行うのに適している。 インターセプターは問題をオブジェクトではなく、アスペクトに分割するため、これをアスペクト指向プログラミング(AOP)と呼ぶ。
多くの開発者は直接メソッドインターセプターを記述することはないだろうが、Warp Persistのような統合ライブラリで使用されるのを見たことがあるかもしれない。 そこでは、マッチするメソッドの選択、インターセプターの作成と構成が必要になる。
マッチャーは値を受け入れるか拒否するかを決めるシンプルなインターフェースである。 GuiceのAOPでは、二つのマッチャーが必要であり、一つはどのクラスを参加させるかであり、もう一つはそれらのクラスのメソッドを選択するものである。 これを簡単にするために、よくあるシナリオでの利用を満足するファクトリを用意している。
MethodInterceptorsはマッチするメソッドが呼び出されるたびに実行される。 それらは、呼び出しを調査する機会が与えられる、メソッドそれ自身、引数、そしてメソッド呼び出し対象のインスタンスである。 そしてそれ自身のクロスカッティングロジックを実行し、実装メソッドを実行することができる。 最終的に、返り値や例外を調査して復帰する。 インターセプタは多くのメソッドに適用され多数呼び出されるので、その実装は効率的でかつ出しゃばりでないことが求められる。
例:週末にはメソッド呼び出しを禁止する
Guiceのインターセプタがどのように動作するかを示すために、週末にはピザ代金受領システムの呼び出しを禁止することにしよう。 配達要員は月曜から金曜までしか働かないので、配達できないときはピザの注文ができないようにする! この例の構造はオーサライゼーションにAOPを使う場合によく似ている。
メソッドを「ウィークデーのみ」とマークするためのアノテーションを定義する。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface NotOnWeekends {}
インターセプトされるメソッドに適用する。
public class RealBillingService implements BillingService { @NotOnWeekends public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { ... } }
次に、org.aopalliance.intercept.MethodInterceptorインターフェースを実装したインターセプターを作成する。 実装メソッドの呼び出しが必要な場合には、invocation.proceed()を呼び出すことにより実行する。
public class WeekendBlocker implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { Calendar today = new GregorianCalendar(); if (today.getDisplayName(DAY_OF_WEEK, LONG, ENGLISH).startsWith("S")) { throw new IllegalStateException( invocation.getMethod().getName() + " not allowed on weekends!"); } return invocation.proceed(); } }
最後にすべてを構成する。 これは、インターセプトされるべきクラスとメソッドを決めるマッチャーを作成すべき場所である。 このケースでは、どのクラスでも適用するが、ただし@NotOnWeekendsアノテーション付のメソッドのみとする。
public class NotOnWeekendsModule extends AbstractModule { protected void configure() { bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), new WeekendBlocker()); } }
すべてを一緒にし(そして土曜まで待ち)、メソッドがインターセプトされて注文が拒否されることを見てみよう。
Exception in thread "main" java.lang.IllegalStateException: chargeOrder not allowed on weekends! at com.publicobject.pizza.WeekendBlocker.invoke(WeekendBlocker.java:65) at com.google.inject.internal.InterceptorStackCallback.intercept(...) at com.publicobject.pizza.RealBillingService$$EnhancerByGuice$$49ed77ce.chargeOrder(<generated>) at com.publicobject.pizza.WeekendExample.main(WeekendExample.java:47)}} }
制限事項
舞台裏では、メソッドインターせぷしょンはバイトコードを生成することにより実装されている。 Guiceは動的にサブクラスを作成し、メソッドをオーバライドすることによりインターセプターを適用する。 もし、バイトコード生成をサポートしていないプラットフォーム(Androidなど)の場合、AOPサポート無しでGuiceを使用しなければならない。
このアプローチでは、インターセプト可能なクラスとメソッドに次のような制限がある。
- クラスはpublicあるいはパッケージprivateでなければならない。
- クラスはfinalであってはならない。
- メソッドはpublic、パッケージprivate、protectedのいずれかでなくてはならない。
- メソッドはfinalであってはならない。
- インスタンスはGuiceが作成したものでなくてはならない。@Injectアノテーションがついている、あるいは引数無しのコンストラクタによって。
Guiceが生成したものではないインスタンスについてメソッドインターセプションを行うことはできない。
インターセプターに注入する
インターセプター自体に依存性注入したい場合はrequestInjection APIを用いる。
public class NotOnWeekendsModule extends AbstractModule { protected void configure() { WeekendBlocker weekendBlocker = new WeekendBlocker(); requestInjection(weekendBlocker); bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), weekendBlocker); } }
インターセプターに注入する場合は注意する。 もしインターセプターが、インターセプトしているメソッドを呼び出してしまうと、StackOverflowExceptionが発生する。
AOP アライアンス
Guiceによって実装されるメソッドインターセプターAPIはAOPアライアンスと呼ばれるパブリック仕様の一部である。 これにより、同じインターセプターを他複数のフレームワークにて使うことができる。