MethodInterceptor を interface で書く

昨日書いた投稿に触発されて、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 guiceAOP する為に定義する 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 で書くことで、インターセプタ対象のクラスを
かなり自由に書けるようになるはずだ。