■
[読Seasar] アスペクトの織り込み
以前の記事で、ComponentDeployer の初期化時に、デプロイ対象とするクラスにアスペクトを織り込むことが分かっています。AopProxyUtil#getConcreteClass(ComponentDef componentDef) で、アスペクトを織り込んだクラスを生成するのでした。ソースコードを再掲します。
/** * 完全なクラスを返します。 * * @param componentDef * @return 完全なクラス */ public static Class getConcreteClass(final ComponentDef componentDef) { if (componentDef.getAspectDefSize() == 0 && componentDef.getInterTypeDefSize() == 0) { return componentDef.getComponentClass(); } final Map parameters = new HashMap(); parameters.put(ContainerConstants.COMPONENT_DEF_NAME, componentDef); AopProxy proxy = new AopProxy(componentDef.getComponentClass(), getAspects(componentDef), getInterTypes(componentDef), parameters); return proxy.getEnhancedClass(); }
/** * {@link AopProxy}を作成します。 * * @param targetClass * @param aspects * @param interTypes * @param parameters */ public AopProxy(final Class targetClass, final Aspect[] aspects, final InterType[] interTypes, final Map parameters) { this.targetClass = targetClass; defaultPointcut = new PointcutImpl(targetClass); weaver = new AspectWeaver(targetClass, parameters); setupAspects(aspects); weaver.setInterTypes(interTypes); enhancedClass = weaver.generateClass(); }
defaultPointcut は、対象となるクラスが実装している全インターフェースのメソッドが対象となるポイントカット。AspectWeaver は後で使っているので一旦飛ばし、まず setupAspects を見てみる。
private void setupAspects(Aspect[] aspects) { if (aspects == null || aspects.length == 0) { return; } for (int i = 0; i < aspects.length; ++i) { Aspect aspect = aspects[i]; if (aspect.getPointcut() == null) { aspect.setPointcut(defaultPointcut); } } Method[] methods = targetClass.getMethods(); for (int i = 0; i < methods.length; ++i) { Method method = methods[i]; if (MethodUtil.isBridgeMethod(method) || MethodUtil.isSyntheticMethod(method)) { continue; } List interceptorList = new ArrayList(); for (int j = 0; j < aspects.length; ++j) { Aspect aspect = aspects[j]; if (aspect.getPointcut().isApplied(method)) { interceptorList.add(aspect.getMethodInterceptor()); } } if (interceptorList.size() == 0) { continue; } weaver.setInterceptors(method, (MethodInterceptor[]) interceptorList .toArray(new MethodInterceptor[interceptorList .size()])); } }
少々省略したけど、「うわぁ・・・」って感じ。分かりづらい・・・。
まず、アスペクトに対してポイントカットが無ければ defaultPointcut をセットしている。次に対象クラスのメソッドに対してインターセプタを仕込んでいる。この際、ブリッジメソッドか合成メソッドはこの処理を飛ばしている。記憶が曖昧だけど、確かどちらもコンパイラが自動生成するメソッドなので、無駄にインターセプタが仕込まれるのを省略してるんだろう。
/** * {@link MethodInterceptor}を設定します。 */ public void setInterceptors(final Method method, final MethodInterceptor[] interceptors) { final String methodInvocationClassName = getMethodInvocationClassName(method); final MethodInvocationClassGenerator methodInvocationGenerator = new MethodInvocationClassGenerator( classPool, methodInvocationClassName, enhancedClassName); final String invokeSuperMethodName = createInvokeSuperMethod(method); methodInvocationGenerator.createProceedMethod(method, invokeSuperMethodName); enhancedClassGenerator.createTargetMethod(method, methodInvocationClassName); final Class methodInvocationClass = methodInvocationGenerator .toClass(ClassLoaderUtil.getClassLoader(targetClass)); setStaticField(methodInvocationClass, "method", method); setStaticField(methodInvocationClass, "interceptors", interceptors); setStaticField(methodInvocationClass, "parameters", parameters); methodInvocationClassList.add(methodInvocationClass); }
う、ここから重い・・・。MethodInvocationClass って何だ?
/** * <code>proceed</code>メソッドのソースを作成します。 * * @param targetMethod * @param enhancedClassName * @param invokeSuperMethodName * @return <code>proceed</code>メソッドのソース */ public static String createProceedMethodSource(final Method targetMethod, final String enhancedClassName, final String invokeSuperMethodName) { final StringBuffer buf = new StringBuffer(1000); buf.append("{"); buf.append("if (interceptorsIndex < interceptors.length) {"); buf.append("return interceptors[interceptorsIndex++].invoke(this);"); buf.append("}"); buf.append(createReturnStatement(targetMethod, enhancedClassName, invokeSuperMethodName)); buf.append("}"); return new String(buf); }
と思ったら、メソッドをコールする際に全てのインターセプタを invoke するためのクラスを生成するものだった。で、この methodInvocationClass を使用してエンハンスするクラスのメソッドを生成している。このとき生成されるメソッドの本体は EnhancedClassGenerator#createTargetMethodSource で、methodInvocationClass を new して proceed() するもの。
最後に AspectWeaver#generateClass() で、エンハンスされたクラスを生成。
/** * クラスを生成します。 * * @return 生成されたクラス */ public Class generateClass() { if (enhancedClass == null) { enhancedClass = enhancedClassGenerator.toClass(ClassLoaderUtil .getClassLoader(targetClass)); for (int i = 0; i < methodInvocationClassList.size(); ++i) { final Class methodInvocationClass = (Class) methodInvocationClassList .get(i); setStaticField(methodInvocationClass, "targetClass", targetClass); } } return enhancedClass; }
恐ろしいほど意図が分からない・・・。methodInvocationClass の targetClass プロパティに対して対象クラスを設定している。何でこのタイミングで設定しているのかは謎。まぁ取り敢えずここでエンハンスされたクラスが完成するわけですね。最終的にはここで出来た enhancedClass を newInstance() する。
上記の処理をまとめると、次のようになる。
対象クラスのポイントカットとなるメソッドに対して、インターセプタをセットしていて、1つのメソッドに対するインターセプタは MethodInvocation として纏められる。MethodInvocation は proceed() によって各インターセプタが invoke されるようになっていて、対象メソッドが実行されると、MethodInvocation#proceed() が実行されるような enhancedClass が生成される。
つまり、アスペクトが織り込まれたメソッドの呼び出しは次のように実現される。
- MethodInvocation#proceed()
- Interceptor1#invoke(methodInvocation)
- Interceptor2#invoke(methodInvocation)
- Interceptor3#invoke(methodInvocation)
- ...
- 対象メソッド本体の実行
こんな感じ。うーん、疲れました。次は何を読もうかなぁ・・・。トランザクション制御の辺りは読んでみたいかも。