Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

java.lang.reflect.Proxyによる自作AOPでトレースログ機能を実装

パンくず

Java
java.lang.reflect.Proxyによる自作AOPでトレースログ機能を実装

概要

java.lang.reflect.Proxyによる自作AOPでトレースログ機能を実装

内容

java.lang.reflect.Proxy
を利用して、AOPによるTraceログ機能を実装します。

※補足
AOPアスペクト指向プログラミング(Aspect Oriented Programming)
プログラムに共通する横断的要素などを「アスペクト」として
モジュール化することで。変更・保守を用意にする手法。
既存コードに手を加えずに共通機能を実現出来る。
掲題のロギングが代表的な実用例。

構成

┗─aop
      Hoge.java:ログ出力対象のクラス
      HogeIntf.java:ログ出力対象のクラスのインターフェース
      SampleProxy.java:mainメソッドからの実行用クラス
      TraceIntercepter.java:トレースログ出力インターセプター
      TraceLoggable.java:トレースログ出力機能を各クラスに提供するクラス

TraceIntercepter.javaとTraceLoggable.javaだけあれば
好きなクラスにトレースログ出力機能を実現可能です。
ログの出力内容を変えたければTraceIntercepter.javaをいじればいい。
※実際に業務に適用するならTraceIntercepter.java内部のログ出力内容も部品化して
別クラスにユーティリティ化すると思いますけど。

サンプル

HogeIntf.java

package aop;

public interface HogeIntf {
  String getHoge();

  void setHoge(String hoge);
}

Hoge.java

package aop;

import java.util.concurrent.TimeUnit;

public class Hoge implements HogeIntf {
  private String hoge;

  public Hoge(String hoge) {
    super();
    this.hoge = hoge;
  }

  public static HogeIntf getProxyInstance(HogeIntf hoge) {
    return TraceLoggable.getProxyInstance(hoge);
  }

  @Override
  public String getHoge() {
    waitRandom();
    return hoge;
  }

  @Override
  public void setHoge(String hoge) {
    waitRandom();
    this.hoge = hoge;
  }

  private void waitRandom() {
    try {
      Double rand = Math.random() * 10;
      TimeUnit.MILLISECONDS.sleep(rand.longValue());
    } catch (InterruptedException e) {
      // sampleなので気にしない
    }
  }
}

TraceIntercepter.java

package aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TraceIntercepter implements InvocationHandler {
  Object target;

  public TraceIntercepter(Object target) {
    this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    long start = System.currentTimeMillis();
    outputStarTrace(method, args, start);

    // 実際のコードを呼び出し
    Object ret = method.invoke(target, args);

    long end = System.currentTimeMillis();
    long total = end - start;
    outputEndTrace(method, ret, end, total);
    return ret;
  }

  private void outputStarTrace(Method method, Object[] args, long start) {
    System.out.println("Trace Start|" + start + "msec|" + target.getClass() + "|" + method.getName() + "|---msec");
    if (args != null && args.length != 0) {
      for (int i = 0; i < args.length; i++) {
        outputArg(args[i]);
      }
    }
  }

  private void outputEndTrace(Method method, Object ret, long end, long total) {
    System.out.println("Trace End|" + end + "msec|" + target.getClass() + " : " + method.getName() + "|" + total
        + "msec");
    if (ret != null) {
      outputReturn(ret);
    }
  }

  private void outputArg(Object value) {
    outputTypeValue(value, "引数の値|");
  }

  private void outputReturn(Object value) {
    outputTypeValue(value, "戻り値の値|");
  }

  private void outputTypeValue(Object value, String preMessage) {
    System.out.println(preMessage + value.getClass().getSimpleName() + "=" + value.toString());
  }

}

TraceLoggable.java

package aop;

import java.lang.reflect.Proxy;

public class TraceLoggable {
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static <T> T getProxyInstance(T instance) {
    Class<? extends Object> clazz = instance.getClass();
    // 対象クラスが実装するインターフェースのリスト
    Class[] classes = clazz.getInterfaces();
    TraceIntercepter intercepter = new TraceIntercepter(instance);
    T proxyInstance = (T) Proxy.newProxyInstance(clazz.getClassLoader(), classes, intercepter);
    return proxyInstance;
  }
}

SampleProxy.java

package aop;

public class SampleProxy {
  public static void main(String[] args) {
    HogeIntf proxyHoge = Hoge.getProxyInstance(new Hoge("hoge"));
    proxyHoge.setHoge("newHoge");
    proxyHoge.getHoge();
  }
}

出力

Trace Start|1353867111031msec|class aop.Hoge|setHoge|---msec
引数の値|String=newHoge
Trace End|1353867111032msec|class aop.Hoge : setHoge|1msec
Trace Start|1353867111032msec|class aop.Hoge|getHoge|---msec
Trace End|1353867111040msec|class aop.Hoge : getHoge|8msec
戻り値の値|String=newHoge