昨日書いた投稿に触発されて、AOPについて考える。
現在の yipuran-mybatis の原型を書いたのは 2010年でまだ、Java6 だった。
DB接続コネクションとトランザクションを AOP でまとめたのが yipuran-mybatis であったが、
使用する時に、IBatisDAO クラスなる抽象クラスを継承させることが、AOP のインターセプタ対象の
条件の1つになってしまった。
java 8 以降 インターフェース デフォルト実装では、インターセプタをインターフェース デフォルト実装に
持ってくることができるはずだ。
yipuran-mybatis を書き始めたのが、2010年でまだ Java6 で、運用実績もあり
もう大がかりな改編はしたくない。残念だ。
yipuran-mybatis ではないが、以下、
インターセプタをインターフェース デフォルト実装のサンプル
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * XLaboInterceptor */ public interface XLaboInterceptor extends MethodInterceptor{ @Override default Object invoke(MethodInvocation m) throws Throwable{ Object rtn = null; // 前処理 System.out.println("=== previous process ==="); rtn = m.proceed(); // 後処理 System.out.println("=== after process ==="); return rtn; } }
これを実装したものを Google guice でAOP する為に定義する Module クラスの実装で、
これを生成するパラメータで、上の XLaboInterceptor インスタンスを受け取る
import com.google.inject.AbstractModule; import com.google.inject.matcher.Matchers; import com.google.inject.name.Names; /** * XLaboModule */ public class XLaboModule extends AbstractModule{ private String key; private XLaboInterceptor interceptors; public XLaboModule(String anotateName, XLaboInterceptor interceptors){ key = anotateName; this.interceptors = interceptors; } @Override protected void configure(){ binder().bindInterceptor(Matchers.any(), Matchers.annotatedWith(Names.named(key)), interceptors); } }
インターセプタ実行される側の処理、→ XLaboFace と その実装
public interface XLaboFace{ public void execute(); }
@Namedアノテーション、"ABC" が付いたメソッドがインターセプタ対象、
import com.google.inject.name.Named; /** * XLaboFaceImpl.java */ public class XLaboFaceImpl implements XLaboFace{ @Named("ABC") @Override public void execute(){ System.out.println("XLaboFaceImpl execute()!!"); } }
インターセプタクラス、前処理、後処理実装を書くところだが、何もなくても動く。
/** * AbcInterceptor */ public class AbcInterceptor implements XLaboInterceptor{ }
実行する。
Injector injector = Guice.createInjector(new XLaboModule("ABC", new AbcInterceptor())); XLaboFace labo = injector.getInstance(XLaboFaceImpl.class); labo.execute();
結果。。
=== previous process === XLaboFaceImpl execute()!! === after process ===
まだこれでは、実践的ではないので、もう少し前処置や後処理を書いてみる。
インターフェースにメソッドで実行する前処理実行時、インターセプタ内のフィールドにアクセスする為の
アノテーション
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Title */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Title{ String value(); }
インターセプタとして動作クラスに、前処理や後処理を書く。上の Title アノテーションも使用する。
/** * AbcInterceptor */ public class AbcInterceptor implements XLaboInterceptor{ @Title("on") private String title = null; @Override public Object previous(){ System.out.println("# AbcInterceptor : previous() : title = " + title ); return title + " " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")); } @Override public void after(Object obj){ System.out.println("# AbcInterceptor : after() : object = " + obj ); } }
XLaboInterceptor を書き直す
import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.Optional; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * XLaboInterceptor */ public interface XLaboInterceptor extends MethodInterceptor{ // 前処理 public Object previous(); // 後処理 public void after(Object obj); @Override default Object invoke(MethodInvocation m) throws Throwable{ Object rtn = null; // 前処理 System.out.println("=== previous process ==="); // このインターセプタ実装クラス内の Title アノテーション宣言 Field を検索して任意title名をつける Field f = getAnotatedField(Title.class, getClass()); if (f != null){ String value = f.getAnnotation(Title.class).value(); if (Optional.ofNullable(value).filter(v->v.equals("on")).isPresent()){ f.setAccessible(true); f.set(this, "Sample labo"); } } Object obj = previous(); rtn = m.proceed(); // 後処理 System.out.println("=== after process ==="); after(obj); return rtn; } static Field getAnotatedField(Class<? extends Annotation> a, Class<?> cls){ Field rtn = null; Class<?> tcls = cls; while(!tcls.equals(java.lang.Object.class)){ Field[] fls = tcls.getDeclaredFields(); for(int i = 0; i < fls.length; i++){ Annotation[] as = fls[i].getAnnotations(); if (as != null){ for(int k = 0; k < as.length; k++){ if (as[k].annotationType().equals(a)){ rtn = fls[i]; i = fls.length; break; } } } } if (rtn != null) return rtn; tcls = tcls.getSuperclass(); } // アノテーション必須条件にする場合はここで、RuntimeException発生させる return rtn; } }
Titleアノテーションを、@Title("on") と書いている時の結果
=== previous process === # AbcInterceptor : previous() : title = Sample labo XLaboFaceImpl execute()!! === after process === # AbcInterceptor : after() : object = Sample labo 13:03:21
Titleアノテーションを、@Title("off") と書いている時の結果
=== previous process === # AbcInterceptor : previous() : title = null XLaboFaceImpl execute()!! === after process === # AbcInterceptor : after() : object = null 13:07:40
MethodInterceptor の invoke() 実装を interface で書くことで、インターセプタ対象のクラスを
かなり自由に書けるようになるはずだ。