JAVAの記事一覧

Top / cglibを使って動的コード生成

趣旨

jparsecみてたら、動的ソースコード生成のライブラリ使っており興味が湧いたのでまとめてみる

SpringやらHibernateなんかに内部的に使われているライブラリーらしい。

目次

インタフェース定義するだけでPOJO的なインスタンスを生成するサンプル

http://cglib.sourceforge.net/xref/samples/KeySample.html

実行トレーサを作るサンプル

http://cglib.sourceforge.net/xref/samples/Trace.html

JDKのProxyの代わりに使える

なんのことかというと、あたかも元々のクラスのようにつかうことができるサンプルです。

JDKのProxyの説明はこちら

http://ffy.afy-system.jp/tips/t_004.html

サンプルコード

http://cglib.sourceforge.net/xref/samples/JdkCompatibleProxy.html

java cglibでmixin

ネタ元はこちら

プログラマメモ2

http://programamemo2.blogspot.com/2007/07/java-cglibmixin-mixin.html

既に生成されたオブジェクトにたいして、あらたにインターフェイスを付加する

サンプルコード

package mixin;

import net.sf.cglib.proxy.Mixin;

public class Test {

   public static void main(String[] args) {
       new Test().testMixin();
   }

   void testMixin() {

       C c = mixin(new A() {

           @Override
           public void a() {
               System.out.println("o_o i am A!!");
           }
       });

       /* もとの型を保持しているかチェック */
       if (c instanceof A) {
           ((A) c).a();
       }
       c.c();
   }
 
   /*
    * クラス配列とクラス配列を連結します。
    */
   static Class[] concat(Class[] a, Class[] b) {
       Class[] arr = new Class[a.length + b.length];
       System.arraycopy(a, 0, arr, 0, a.length);
       System.arraycopy(b, 0, arr, a.length, b.length);
       return arr;
   }

   interface A {
       public void a();
   }

   interface C {
       public void c();
   }

   /*
    * もとのオブジェクトにたいして、Cインターフェイスを付け加えます。
    */
   public C mixin(Object o) {

       Class[] interfaces = concat(new Class[] { C.class }, o.getClass()
               .getInterfaces());

       Object[] delegates = new Object[] { new C() {

           @Override
           public void c() {
               System.out.println("i am C o_o!");
           }
       }, o };
       Object obj = Mixin.create(interfaces, delegates);

       C c = (C) obj;

       return c;
   }
}

考察

元にするクラスのインタフェースを用意しとかなきゃならんのかな?

あとで下記のドキュメントでもみてみるか。。。

[Java]動的なインターフェイスの追加(擬似的な方法)

http://d.hatena.ne.jp/daisuke-m/20081212/1229083116

メソッド実行の前後に処理をフックをかけるというか差し込む

元ネタサイトはこちら

CGLIBにさわる

http://muimi.com/j/aop/cglib/

メソッド実行の前後に処理をフックをかけるというか差し込む技がつかわれています。

元ネタの元ネタサイトは 本家

http://cglib.sourceforge.net/

Sampleの

Beansサンプルと思われる。

掲載されていたサンプル

Foo.java

package hoge;

public class Foo {

   public void doSomething(){
       System.out.println("doSomethig");
   }
}

Sample1.java

package hoge;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;


public class Sample1 {

   public static void main(String[] args) throws Exception{
        
       Enhancer enhancer = new Enhancer();
       enhancer.setSuperclass(Foo.class);
       enhancer.setCallback(new MyMethodIntercepter());
       Foo foo = (Foo)enhancer.create();
       foo.doSomething();
   }
}
class MyMethodIntercepter implements MethodInterceptor{

   public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
       System.out.println("before");
       return proxy.invokeSuper(obj, args);
   }
    
}

実行結果

before
doSomethig

Enhancerというクラスを利用してサブクラスを作り、setCallback()で差込みを行うようです。

CGLIB で実行時にクラスにメソッドを追加する

URLはこちら

http://d.hatena.ne.jp/akishin999/20100604/1275611622

サンプルコード

以下のサンプルでは、Date 型の setCreatedAt? しか持たない JavaBean? に対して、文字列を引数として同名のメソッドを呼び出せるようにしています。

ポイントは InterfaceMaker? を使ってインターフェースの定義をして、Enhancer で作成したインターフェースを元のクラスに追加してやるところになります。

package example;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

import net.sf.cglib.asm.Type;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InterfaceMaker;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CGLibExample {

   @SuppressWarnings("unchecked")
   public static void main(String[] args) {
       // 新規インターフェースを定義する
       InterfaceMaker im = new InterfaceMaker();
       // 文字列を引数とした setCreatedAt を定義
       im.add(new Signature("setCreatedAt", Type.VOID_TYPE, new Type[] { Type
               .getType(String.class) }), null);

       // インターフェースを生成
       Class myInterface = im.create();

       Enhancer enhancer = new Enhancer();
       enhancer.setSuperclass(ExampleBean.class);
       // 生成したインターフェースを追加する
       enhancer.setInterfaces(new Class[] { myInterface });
       enhancer.setCallback(new MethodInterceptor() {
           public Object intercept(Object obj, Method method, Object[] args,
                   MethodProxy proxy) throws Throwable {
                
               ExampleBean bean = (ExampleBean) obj;
                
               // 文字列を引数とした setCreatedAt が呼ばれた場合に Date 型に変換し本来の Setter を呼び出す。
               if (method.getName().startsWith("setCreatedAt")
                       && args[0] != null && args[0] instanceof String) {

                   SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
                   Date date = null;
                   try {
                       date = sdf.parse((String) args[0]);
                   } catch (final Exception e) { /* nop */ }
                   bean.setCreatedAt(date);
                   return null;

               }
               return proxy.invokeSuper(obj, args);
           }
       });

       // Bean を生成
       ExampleBean bean = (ExampleBean) enhancer.create();
       bean.setId(999);

       // 実行時に型を追加しているため、呼び出しはリフレクション経由
       try {
           // 追加したメソッドはあくまで CGLIB によって作成された型にしか存在しないため、
           // ExampleBean.class ではなく、bean.getClass() のようにして指定する必要がある。
           Method method = bean.getClass().getMethod("setCreatedAt", new Class[] {String.class});
           method.invoke(bean, new Object[]{"20100531"});
       } catch (final Exception e) {
           e.printStackTrace();
       }
        
       System.out.printf("id : [%d] createdAt : [%s]\n", bean.getId(), bean.getCreatedAt());
   }
}

/**
 * サンプル用の JavaBeans
 */
class ExampleBean implements Serializable {
   private static final long serialVersionUID = -8121418052209958014L;
    
   private int id;
   private Date createdAt;
 
   public int getId() {
       return id;
   }

   public void setId(int id) {
       this.id = id;
   }

   public Date getCreatedAt() {
       return createdAt;
   }

   public void setCreatedAt(Date createdAt) {
       this.createdAt = createdAt;
   }

}

実行結果

id : [999] createdAt : [Mon May 31 00:00:00 JST 2010]

考察

インタフェースだけ定義しておいて、テスト用にモックオブジェクトを返すような開発ができるため、 分業することができる。

jmock

http://www.jmock.org/mocking-classes.html

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS