Seasar2でアノテーションを使って自動AOPその4
えーと・・・また問題点が出てきてしまいました(汗)
軽い勉強のつもりで、作ったソースをここに載せたのはいいけど、こうミスが目立つと自分の恥を晒してるような気がしてきた・・・
Seasar2は、インターフェイス実装オブジェクトの自動バインディング機能を利用することによって、設定ファイルの記述を最小限に減らすことが出来ます。この機能を有効に利用するには、例えば設定ファイルを業務毎に細かく分けるような運用を行って、予期しない自動バインディングが極力起こらないように工夫するのが通常ではないかと思います。ただ、その一方で、システム共通で利用するようなコンポーネントもあります。データソースオブジェクトやアスペクト関連コンポーネント等がそれですね。S2は、親ファイルから子ファイルの設定を見に行くことは出来ても、逆が出来ませんから、共通コンポーネントを一箇所のincludeで処理することは不可能です。自分の場合は、そういう共通コンポーネントを一つの設定ファイルにまとめて、それを各設定ファイルでincludeするようにしています。
・・・で、その共通コンポーネントに対してアノテーションを使ったアスペクトを利用しようとした場合、自分が記述したようなやり方だと、共通コンポーネントを登録したS2Containerが何度も呼ばれて、アスペクトが二重三重に設定されることになってしまいます。つまり、一度処理したS2Containerオブジェクトをチェックして、二重に処理をしないようにしないといけないってことですね。
・・・そろそろソースの変更も多すぎる状態になってきたので、ここで再度ソースを書いてみます。
AutoRegisterインターフェイス
package test; public interface AutoRegister { void registAll(); }
AbstractAutoRegisterクラス
自動AOP設定を行う基底クラス
package test; import java.util.HashSet; import java.util.Set; import org.seasar.framework.container.ComponentDef; import org.seasar.framework.container.S2Container; public abstract class AbstractAutoRegister implements AutoRegister { private S2Container container; private Set<S2Container> set; public void registAll() { set = new HashSet<S2Container>(); S2Container root = container.getRoot(); registContainer(root); set = null; } private void registContainer(S2Container container) { if (!set.contains(container)) { for (int i = 0; i < container.getComponentDefSize(); i++) { ComponentDef cd = container.getComponentDef(i); regist(cd); } for (int i = 0; i < container.getChildSize(); i++) { registContainer(container.getChild(i)); } set.add(container); } } protected abstract void regist(ComponentDef componentDef); public S2Container getContainer() { return container; } public void setContainer(S2Container container) { this.container = container; } }
AnnotationAutoRegisterクラス
このクラスに指定したアノテーションを記述しているコンポーネントを探して、AOP定義を追加するクラス
package test; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import org.seasar.framework.aop.Pointcut; import org.seasar.framework.aop.impl.PointcutImpl; import org.seasar.framework.container.AspectDef; import org.seasar.framework.container.ComponentDef; import org.seasar.framework.container.impl.AspectDefImpl; public class AnnotationAutoRegister extends AbstractAutoRegister { private Class annotationClass; private String interceptorName; @Override protected void regist(ComponentDef componentDef) { Class componentClass = componentDef.getComponentClass(); if (componentClass == null) { return; } Pointcut pointcut = null; if (componentClass.isAnnotationPresent(annotationClass)) { pointcut = new PointcutImpl(componentClass); } if (pointcut == null) { Set<String> set = new HashSet<String>(); for (Method method : componentClass.getMethods()) { if (method.isAnnotationPresent(annotationClass)) { set.add(method.getName()); } } if (set.size() >= 1) { pointcut = new PointcutImpl(set.toArray(new String[set.size()])); } } if (pointcut != null) { AspectDef aspectDef = new AspectDefImpl(pointcut); aspectDef.setExpression(interceptorName); componentDef.addAspectDef(aspectDef); } } public Class getAnnotationClass() { return annotationClass; } public void setAnnotationClass(Class classObj) { this.annotationClass = classObj; } public String getInterceptorName() { return interceptorName; } public void setInterceptorName(String interceptorName) { this.interceptorName = interceptorName; } }
AutoRegisterInitクラス
コンテナに登録されているAutoRegisterを探し出して初期化処理を行うクラス
package test; import java.util.HashSet; import java.util.Set; import org.seasar.framework.container.ComponentDef; import org.seasar.framework.container.S2Container; public class AutoRegisterInit { private S2Container container; private Set<S2Container> set; public void init() { set = new HashSet<S2Container>(); S2Container root = container.getRoot(); initContainer(root); set = null; } private void initContainer(S2Container container) { if (!set.contains(container)) { for (int i = 0; i < container.getComponentDefSize(); i++) { ComponentDef cd = container.getComponentDef(i); Class componentClass = cd.getComponentClass(); if (AutoRegister.class.isAssignableFrom(componentClass)) { String name = cd.getComponentName(); AutoRegister obj = (AutoRegister) container.getComponent(name); obj.registAll(); } } for (int i = 0; i < container.getChildSize(); i++) { initContainer(container.getChild(i)); } set.add(container); } } public S2Container getContainer() { return container; } public void setContainer(S2Container container) { this.container = container; } }
AnnotationInterceptor
このクラスに指定したアノテーションを定義しているメソッドに対してAOPを行うMethodInterceptor。メソッドに定義が無くてもクラスに定義があれば実行する。このクラスによってオーバーロードされたメソッドの中からAOPする対象を選択できる。
package test; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public abstract class AnnotationInterceptor implements MethodInterceptor { private Class annotationClass; public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); Class declaringClass = method.getDeclaringClass(); if (declaringClass.isAnnotationPresent(annotationClass) || method.isAnnotationPresent(annotationClass)) { return rootInvoke(invocation); } return invocation.proceed(); } protected abstract Object rootInvoke(MethodInvocation invocation) throws Throwable; protected Annotation getAnnotation(MethodInvocation invocation) { Annotation annotation = null; Method method = invocation.getMethod(); if (method.isAnnotationPresent(Transaction.class)) { annotation = method.getAnnotation(Transaction.class); } else { Class targetClass = method.getDeclaringClass(); annotation = targetClass.getAnnotation(Transaction.class); } return annotation; } public Class getAnnotationClass() { return annotationClass; } public void setAnnotationClass(Class annotationClass) { this.annotationClass = annotationClass; } }
・・・ふう、これで同じAspectが多重にかからないようになりました。フィールドに一時的情報を持つようになったから、AutoRegisterは一応prototypeで登録しとくべきかな? まぁ初期化専用なのであまり気にしなくてもいいんでしょうけど。
(追記)AutoRegisterInitをprototypeにすると、getComponentするまでinitメソッドが呼ばれないから上手く動かなくなるんですね。まぁよく考えてみたら当然か・・・
あと、ふと思ったのですが・・・Seasar2の設定ファイル分割の仕組みは非常に便利で、バインディングの範囲を狭める意味でも非常に有効だとは思うのですが・・・それとは別に、設定コンポーネントから共通に見ることが出来る「GlobalなComponent」を定義できる設定があったらいいなと思いました。・・・あ、でもグローバル変数のように混乱する原因になっちゃうかもな・・・でも、そういう機能があったら便利だとは思います。