Seasar2.3でアノテーションを使って自動AOP
かなり日記がご無沙汰になってました(汗)
本格的に開発の仕事に入ったのと、現実逃避の為に家で真メガテン3マニアックスにハマっていたので(笑)、結構忙しい日々を送ってました。
さて、どうやらSeasar2.3 RC1がリリースされたみたいですね。今丁度仕事でS2を使用しているので、早速触ってみました。
コンポーネントの自動設定の為に、クラスのパッケージ名を指定するのですが、これは親のパッケージだけ書いてやれば、子のパッケージまでしっかりチェックしてくれるみたいです。業務用に作ったパッケージの親を指定してやれば、まとめてたくさんのクラスを自動的に登録することが出来そうです。これはかなり便利そう。あと、アノテーションにも本格対応したみたいで、コンポーネント定義やアスペクトの定義をメタデータで指定できるようになりました。
留意点としては、JARにまとめたクラスについては動かないことと、アスペクトのアノテーションをメソッドレベルで指定できないことでしょうか? 以前自分はアノテーションでアスペクトをメソッドレベルで定義する自作クラスを作っていて、それを今仕事でも使用しているのですが・・・何とか自分の自作アノテーションをこのS2.3で動かせないか、考えてみました。
・・・で、いつもの通りソースを・・・
AnnotationInterceptorクラス
以前作ったもの、アノテーションをつけたメソッドやクラスに対してアスペクトをかけるクラス
package test.interceptor; 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<? extends Annotation> 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(annotationClass)) { annotation = method.getAnnotation(annotationClass); } else { Class<?> targetClass = method.getDeclaringClass(); annotation = targetClass.getAnnotation(annotationClass); } return annotation; } public Class<? extends Annotation> getAnnotationClass() { return annotationClass; } public void setAnnotationClass(Class<? extends Annotation> annotationClass) { this.annotationClass = annotationClass; } }
AnnotationMethodInterceptorクラス
あるアノテーションを定義したメソッドやクラスに対して、既存のMethodInterceptorを実行するクラス
package test.interceptor; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class AnnotationMethodInterceptor extends AnnotationInterceptor { private MethodInterceptor interceptor; public void setInterceptor(MethodInterceptor interceptor) { this.interceptor = interceptor; } @Override protected Object rootInvoke(MethodInvocation invocation) throws Throwable { return interceptor.invoke(invocation); } }
AnnotationAspectAutoRegisterクラス
以前作った、自作のAutoRegisterの一部分のロジックを使って、AspectAutoRegisterを継承してみました
package test.interceptor; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.aopalliance.intercept.MethodInterceptor; 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.annotation.Binding; import org.seasar.framework.container.annotation.BindingType; import org.seasar.framework.container.autoregister.AspectAutoRegister; import org.seasar.framework.container.impl.AspectDefImpl; public class AnnotationAspectAutoRegister extends AspectAutoRegister { private Map<Class<? extends Annotation>, String> interceptorMap; public void setInterceptorMap( Map<Class<? extends Annotation>, String> interceptorMap) { this.interceptorMap = interceptorMap; } @Override @Binding(bindingType=BindingType.NONE) public void setInterceptor(MethodInterceptor interceptor) { super.setInterceptor(interceptor); } @Override protected void registInterceptor(ComponentDef componentDef) { Class<?> componentClass = componentDef.getComponentClass(); for (Class<? extends Annotation> annotationClass : interceptorMap.keySet()) { 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(interceptorMap.get(annotationClass)); componentDef.addAspectDef(aspectDef); } } } }
・・・んで、テスト用クラス
package test.inject; public interface Test { String getMessage(); }
package test.inject.impl; import test.annotation.Trace; import test.annotation.Transaction; import test.inject.Test; public class TestImpl implements Test { @Transaction public String getMessage() { return getMessage2(); } @Trace public String getMessage2() { return "こんにちは"; } }
package test.client; public interface TestClient { void execute(); }
package test.client.impl; import test.client.TestClient; import test.inject.Test; public class TestClientImpl implements TestClient { private Test test; public TestClientImpl(Test test) { this.test = test; } public void execute() { System.out.println(test.getMessage()); } }
package test.annotation; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface Trace { }
package test.annotation; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface Transaction { }
package test.main; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.SingletonS2ContainerFactory; import test.client.TestClient; public class Main { /** * @param args */ public static void main(String[] args) { SingletonS2ContainerFactory.init(); S2Container container = SingletonS2ContainerFactory.getContainer(); TestClient client = (TestClient) container.getComponent(TestClient.class); client.execute(); } }
で、最後にapp.dicon
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.3//EN" "http://www.seasar.org/dtd/components23.dtd"> <components> <include path="aop.dicon"/> <include path="j2ee.dicon"/> <component class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister"> <property name="autoNaming"> <component class="org.seasar.framework.container.autoregister.DefaultAutoNaming"/> </property> <initMethod name="addClassPattern"> <arg>"test"</arg> <arg>".*Impl"</arg> </initMethod> <initMethod name="registAll"/> </component> <component class="test.interceptor.AnnotationAspectAutoRegister"> <property name="interceptorMap"> #{@test.annotation.Trace@class : "annotationTraceInterceptor", @test.annotation.Transaction@class : "annotationTransactionInterceptor"} </property> <initMethod name="addClassPattern"> <arg>"test"</arg> <arg>".*Impl"</arg> </initMethod> <initMethod name="registAll"/> </component> <component name="annotationTraceInterceptor" class="test.interceptor.AnnotationMethodInterceptor"> <property name="interceptor">aop.traceInterceptor</property> <property name="annotationClass">@test.annotation.Trace@class</property> </component> <component name="annotationTransactionInterceptor" class="test.interceptor.AnnotationMethodInterceptor"> <property name="interceptor">j2ee.requiredTx</property> <property name="annotationClass">@test.annotation.Transaction@class</property> </component> </components>
これで実行してみると・・・
DEBUG 2005-10-13 04:47:20,488 [main] トランザクションを開始しました DEBUG 2005-10-13 04:47:20,504 [main] BEGIN test.inject.impl.TestImpl#getMessage2() DEBUG 2005-10-13 04:47:20,504 [main] END test.inject.impl.TestImpl#getMessage2() : こんにちは DEBUG 2005-10-13 04:47:20,504 [main] トランザクションをコミットしました こんにちは
・・・アスペクトをかける順番とか気になりますが、とりあえず上手く動いたっぽい。どうしよう・・・2.3に上げてみるべきか・・・設定ファイルが消えるのは凄い魅力的だし・・・