AOPの使い方
本格的なAOPにも及ばない(らしい)が、Guiceでは簡単にそのパワーを利用することができる。 簡単に言えば、「メソッド呼び出しの前後に好きな処理を後から付け加えることができる機能」と思えばよい。 「どのような処理」をつけ加えるかは、もちろんプログラムによって自在に指定することができる。 「どのメソッド」に付け加えるかは、様々な指定方法がある。
簡単なサンプル
以下はすべてのクラス(もちろんGuiceがそのインスタンスを生成するクラス)の中の特定のアノテーションがつけられたメソッドについて、好みの処理を前後に付け加える例である。
import java.lang.annotation.*; import org.aopalliance.intercept.*; import com.google.inject.*; import com.google.inject.matcher.*; public class Sample { /** インターセプトするメソッドの目印となるアノテーション */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface DebugTrace {} /** インターセプト対象のメソッドを持つクラス */ public static class Greeting { @DebugTrace public void hello() { System.out.println("hello, world"); } } /** インターセプタ定義 */ public static class DebugTracer implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before "); Object r = invocation.proceed(); System.out.println("after"); return r; } } /** テスト */ public static void main(String[]args) { Injector injector = Guice.createInjector( new AbstractModule() { protected void configure() { bindInterceptor( Matchers.any(), Matchers.annotatedWith(DebugTrace.class), new DebugTracer() ); } } ); Greeting greeting = injector.getInstance(Greeting.class); greeting.hello(); System.out.println(""); System.out.println("" + greeting.getClass()); System.out.println("" + greeting.getClass().getClassLoader()); System.out.println("" + Sample.class.getClassLoader()); } }
実行結果は以下
before hello, world after class aop.Sample$Greeting$$EnhancerByGuice$$c979486 sun.misc.Launcher$AppClassLoader@a90653 sun.misc.Launcher$AppClassLoader@a90653
生成されるインスタンスはGuiceの用意した一種のプロキシであることに注意。 また、そのクラスローダは通常使われているものと変わらない。
javassist等でバイトコード操作を行って処理を付け加えた場合には、クラスローダが異なってしまうそうだが、 Guiceではそのようなことはない。
クラスとメソッド名を指定する例
以下では特定のクラスとそのサブクラスすべてについて、「set××」というメソッドすべてを対象とする例である。
import java.lang.reflect.*; import org.aopalliance.intercept.*; import com.google.inject.*; import com.google.inject.matcher.*; public class Sample { public static class Greeting { public void hello() { System.out.println("hello, world"); } public void setHello() { System.out.println("setHello"); } } public static class SubGreeting extends Greeting { public void setHi() { System.out.println("setHi"); } } public static class GoodBye { public void setGoodsBye() { System.out.println("setGoodBye"); } } public static void main(String[]args) throws Exception { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { this.bindInterceptor( Matchers.subclassesOf(Greeting.class), new MethodNameMatcher(), new MethodInterceptor() { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before --- " + invocation.getMethod().getName()); Object r = invocation.proceed(); System.out.println("after --- " + invocation.getMethod().getName()); return r; } } ); } }); Greeting greeting = injector.getInstance(Greeting.class); greeting.hello(); greeting.setHello(); SubGreeting subGreeting = injector.getInstance(SubGreeting.class); subGreeting.setHi(); GoodBye goodBye = injector.getInstance(GoodBye.class); goodBye.setGoodsBye(); } public static class MethodNameMatcher extends AbstractMatcher<Method>{ public boolean matches(Method t) { return t.getName().startsWith("set"); } } }
実行結果は以下
hello, world before --- setHello setHello after --- setHello before --- setHi setHi after --- setHi setGoodBye
Matcherの指定
上の例から見てもわかるとおり、どのメソッドを対象とするかはbindInterceptorに指定するクラス指定とメソッド指定によって決まる。 第一引数にはクラス用のMatcher、第二引数にはメソッド用のMatcherを与える。要するに、前者は指定されたクラスが操作対象であるかを判定するものであり、後者は指定されたメソッドを判定する。
Matcherには便利なショートカットが用意されているが、自作するのであれば上記のMethodNameMatcherのようにAbstractMatcherのサブクラスとして、matchesメソッドのみを指定すればよい。
public static class MethodNameMatcher extends AbstractMatcher<Method>{ public boolean matches(Method t) { return t.getName().startsWith("set"); } }
bindInterceptorメソッドの定義は、
void bindInterceptor(Matcher<? super Class<?>> classMatcher, Matcher<? super Method> methodMatcher, org.aopalliance.intercept.MethodInterceptor... interceptors;
であるから、最初の二つの引数として与えることのできるクラス(の型パラメータ)にはそれぞれ制約があることに注意。