アスペクトを織り込むユーティリティを書いた。
となりのチームのプロジェクトが炎上気味でついに出稼ぎに出る羽目になったわけですが、そのプロジェクトではGuiceもSpringもSeasarも使ってないので、アスペクトがさくっと使えない。リリースも近いし、今からモジュール追加するのもメンドウだなー、セットアップのモジュール管理表の締め切りすぎてるしー、ということでアスペクトを織り込むユーティリティを書いてみた。
- java.lang.reflect.Proxyを利用して、簡単なメソッドインターセプターを実現します。
使い方
次のような感じで使えます。
// テスト用クラス class Test implements Runnable { public void run() { System.out.println( "test" ); } } // インスタンスを作成 Runnable r = new Test(); // メソッド前後でメソッド名を出力するアスペクトを適用。 r = Aspect.weave(r, new MethodInterceptor() { public Object invoke(InvocationContext c) throws Throwable { try { System.out.println( "before : " + c.getMethod().getName() ); return c.proceed(); } finally { System.out.println( "after : " + c.getMethod().getName() ); } } }); // メソッド呼び出し r.run();
実行結果です。
before :run test after :run
実装
実装は以下。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Set; /** * Proxyベースの簡易アスペクト。<br/> * 以下の制限があります。 * <ul> * <li>インターフェイスを実装したオブジェクトにのみ、インターセプターを適用できます。</li> * <li>インターセプター適用済みオブジェクトは、元のオブジェクトが実装するインターフェイス型にのみ、キャスト可能です。</li> * <li>オブジェクト内部で、自身のメソッドを呼び出す場合、インターセプターは実行されません。</li> * </ul> */ public class Aspect { /** * アスペクトを織り込む * @param <T> オブジェクトの型 * @param target 折り込み対象(interfaceを実装すること!) * @param interceptors インターセプタ * @return アスペクトを織り込んだオブジェクト */ public static <T> T weave( Object target, MethodInterceptor... interceptors ) { Set<Class<?>> interfaces = collectInterfaces( target.getClass(), new HashSet<Class<?>>()); ClassLoader cl = Thread.currentThread().getContextClassLoader(); InvocationHandler h = new InvocationHandlerImpl(target, interceptors); return (T) Proxy.newProxyInstance( cl, interfaces.toArray(new Class<?>[0]), h ); } /** * クラスが実装するインターフェイスを集める。 * @param c クラス * @param interfaces インターフェイスを格納するSet * @return インターフェイスを格納するSet */ private static Set<Class<?>> collectInterfaces( Class<?> c, Set<Class<?>> interfaces ) { if ( c == null ) { return interfaces; } if ( c.isInterface() ) { interfaces.add(c); } collectInterfaces( c.getSuperclass(), interfaces); for ( Class<?> i : c.getInterfaces() ) { collectInterfaces(i, interfaces); } return interfaces; } /** * 簡易版実行コンテキスト */ public static interface InvocationContext { /** * メソッドを取得する。 * @return メソッド */ Method getMethod(); /** * 引数を取得する。 * @return 引数 */ Object[] getArgs(); /** * 本来の処理を実行する。 * @return 実行結果 * @throws Throwable 例外 */ Object proceed() throws Throwable; } /** * インターセプタ */ public static interface MethodInterceptor { /** * メソッド呼び出し時に実行される。 * @param c 実行コンテキスト * @return 戻り値 * @throws Throwable 何らかのエラーが発生 */ Object invoke( InvocationContext c ) throws Throwable ; } /** * InvocationHandlerの実装。 */ private static final class InvocationHandlerImpl implements InvocationHandler { private final Object target; private final List<MethodInterceptor> interceptors; private InvocationHandlerImpl ( Object target, MethodInterceptor... interceptors ) { this.target = target; this.interceptors = Arrays.asList( interceptors ); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return new InvocationContextImpl( method, args, target, interceptors.listIterator() ).proceed(); } catch ( InvocationTargetException ex ) { Throwable e = ex.getTargetException(); // RuntimeException はそのままスローする。 if ( e instanceof RuntimeException ) { throw e; } // methodのthrow節で定義済みの例外はそのままスローする。 for (Class<?> exc : method.getExceptionTypes()) { if (exc.isInstance(e)){ throw e; } } throw new UndeclaredThrowableException(e); } } } /** * 実行コンテキストの実装。 */ private static final class InvocationContextImpl implements InvocationContext { private final Method m; private final Object[] args; private final Object target; private final ListIterator<MethodInterceptor> interceptors; private InvocationContextImpl( Method m, Object[] args, Object target, ListIterator<MethodInterceptor> interceptors ) { this.m = m; this.args = args; this.target = target; this.interceptors = interceptors; } public Object[] getArgs() { return args; } public Method getMethod() { return m; } public Object proceed() throws Throwable { if ( interceptors.hasNext() ) { try { return interceptors.next().invoke(this); } finally { interceptors.previous(); } } else { return m.invoke( target, args ); } } } }
テストはそれなりにしかやってないので、バグってたらごめん!