JAVAの記事一覧

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

目次

CGLIBとは?

CGLIBとは、Javaコード生成ライブラリです。クラスファイルを実行時に読み込んで編集することが可能です(Javassistと同じジャンル)。CGLIBは、Hibernate、iBatisなどのDBアクセスツールや、Spring、SeasorなどのAOPコンテナなどで利用されています。

CGLIBのWebページ

http://cglib.sourceforge.net/

本ページの趣旨

jparsecみてたら、動的ソースコード生成のライブラリであるcglibを使っており

興味が湧いたのでまとめてみる

ついでに、サンプルのコードの説明がなかったので、自分で説明を試みてみる。

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

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

net.sf.cglib.core.KeyFactory?のすごいのは、なんといっても、インタフェースのnewInstanceメソッドを定義するだけで、これまでのPOJOのgetterやらsetterやらの記述を省略できるのがすごいです。

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

  package samples;
  import net.sf.cglib.core.KeyFactory;
  public class KeySample {
      private interface MyFactory {
          public Object newInstance(int a, char[] b, String d);
      }
      public static void main(String[] args) {
          MyFactory f = (MyFactory)KeyFactory.create(MyFactory.class);
          Object key1 = f.newInstance(20, new char[]{ 'a', 'b' }, "hello");
         Object key2 = f.newInstance(20, new char[]{ 'a', 'b' }, "hello");
         Object key3 = f.newInstance(20, new char[]{ 'a', '_' }, "hello");
         System.out.println(key1.equals(key2));
         System.out.println(key2.equals(key3));
     }
 }

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

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

解説

Enhancer クラスの使い方のサンプル

クラスのインスタンスの作成をnew ではなく

汎用的に作られたnewInstanceメソッドで生成すると

なんと、そのインスタンスのメソッドを実行するたびに、

そのメソッドをインターセプトというかフックと言うべきか横取りして

どこの何のメソッドが実行されているのか、パラメータは何なのか表示します。

サンプル

 
  package samples;
  import net.sf.cglib.proxy.*;
  import java.util.*;
  /***
   *
   * @author  baliuka
   */
  public class Trace implements MethodInterceptor {
       
     int ident = 1;
     static Trace callback = new Trace();
     
     /*** Creates a new instance of Trace */
     private Trace() {
     }
     
     public static  Object newInstance( Class clazz ){
       try{
             Enhancer e = new Enhancer();
             e.setSuperclass(clazz);
             e.setCallback(callback);
             return e.create();
       }catch( Throwable e ){
          e.printStackTrace(); 
          throw new Error(e.getMessage());
       }  
      
     }
     /***
      * @param args the command line arguments
      */
     public static void main(String[] args) {
         List list = (List)newInstance(Vector.class);
         Object value = "TEST";
         list.add(value);
         list.contains(value);
         try{
          list.set(2, "ArrayIndexOutOfBounds" );
         }catch( ArrayIndexOutOfBoundsException ignore ){
           
         }
        list.add(value + "1");
        list.add(value + "2");
        list.toString();
        list.equals(list); 
        list.set( 0, null ); 
        list.toString();
        list.add(list);
        list.get(1);
        list.toArray();
        list.remove(list);
        list.remove("");
        list.containsAll(list);
        list.lastIndexOf(value);
     }

 

   
     public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                             MethodProxy proxy) throws Throwable {
         printIdent(ident);
         System.out.println( method );
         for( int i = 0; i < args.length; i++ ){
           printIdent(ident);   
           System.out.print( "arg" + (i + 1) + ": ");
           if( obj == args[i])
               System.out.println("this");
           else
               System.out.println(args[i]);
         }
         ident++;
  
         Object retValFromSuper = null;
         try {
             retValFromSuper = proxy.invokeSuper(obj, args);
             ident--;
         } catch (Throwable t) {
             ident--;
             printIdent(ident);   
             System.out.println("throw " + t );  
             System.out.println();
             throw t.fillInStackTrace();
         }
         
         printIdent(ident); 
         System.out.print("return " );
         if( obj == retValFromSuper)
             System.out.println("this");
         else System.out.println(retValFromSuper);
         
         if(ident == 1)
              System.out.println();
         
         return retValFromSuper;
     }
       
    void printIdent( int ident ){
        
     
        while( --ident > 0 ){
         System.out.print(".......");
       }
      System.out.print("  ");
   }
}

実行結果

mainメソッドの1行1行実行されるたびに、コンソールにメッセージが表示されてました。

  public synchronized boolean java.util.Vector.add(java.lang.Object)
 arg1: TEST
 return true

 public boolean java.util.Vector.contains(java.lang.Object)
 arg1: TEST
.......  public synchronized int java.util.Vector.indexOf(java.lang.Object,int)
.......  arg1: TEST
.......  arg2: 0
.......  return 0
 return true

 public synchronized java.lang.Object java.util.Vector.set(int,java.lang.Object)
 arg1: 2
 arg2: ArrayIndexOutOfBounds
 throw java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 2

 public synchronized boolean java.util.Vector.add(java.lang.Object)
 arg1: TEST1
 return true

 public synchronized boolean java.util.Vector.add(java.lang.Object)
 arg1: TEST2
 return true

 public synchronized java.lang.String java.util.Vector.toString()
.......  public java.util.Iterator java.util.AbstractList.iterator()
.......  return java.util.AbstractList$Itr@13b06041
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 0
.......  return TEST
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 1
.......  return TEST1
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 2
.......  return TEST2
.......  public synchronized int java.util.Vector.size()
.......  return 3
 return [TEST, TEST1, TEST2]

 public synchronized boolean java.util.Vector.equals(java.lang.Object)
 arg1: this
 return true

 public synchronized java.lang.Object java.util.Vector.set(int,java.lang.Object)
 arg1: 0
 arg2: null
 return TEST

 public synchronized java.lang.String java.util.Vector.toString()
.......  public java.util.Iterator java.util.AbstractList.iterator()
.......  return java.util.AbstractList$Itr@1c701a27
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 0
.......  return null
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 1
.......  return TEST1
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 2
.......  return TEST2
.......  public synchronized int java.util.Vector.size()
.......  return 3
 return [null, TEST1, TEST2]

 public synchronized boolean java.util.Vector.add(java.lang.Object)
 arg1: this
 return true
 public synchronized java.lang.Object java.util.Vector.get(int)
 arg1: 1
 return TEST1
 public synchronized java.lang.Object[] java.util.Vector.toArray()
 return [Ljava.lang.Object;@ffdadcd
 public boolean java.util.Vector.remove(java.lang.Object)
 arg1: this
.......  public synchronized boolean java.util.Vector.removeElement(java.lang.Object)
.......  arg1: this
..............  public int java.util.Vector.indexOf(java.lang.Object)
..............  arg1: this
.....................  public synchronized int java.util.Vector.indexOf(java.lang.Object,int)
.....................  arg1: this
.....................  arg2: 0
............................  public synchronized boolean java.util.Vector.equals(java.lang.Object)
............................  arg1: null
............................  return false
............................  public synchronized boolean java.util.Vector.equals(java.lang.Object)
............................  arg1: TEST1
............................  return false
............................  public synchronized boolean java.util.Vector.equals(java.lang.Object)
............................  arg1: TEST2
............................  return false
............................  public synchronized boolean java.util.Vector.equals(java.lang.Object)
............................  arg1: this
............................  return true
.....................  return 3
..............  return 3
..............  public synchronized void java.util.Vector.removeElementAt(int)
..............  arg1: 3
..............  return null
.......  return true
 return true
 public boolean java.util.Vector.remove(java.lang.Object)
 arg1: 
.......  public synchronized boolean java.util.Vector.removeElement(java.lang.Object)
.......  arg1: 
..............  public int java.util.Vector.indexOf(java.lang.Object)
..............  arg1: 
.....................  public synchronized int java.util.Vector.indexOf(java.lang.Object,int)
.....................  arg1: 
.....................  arg2: 0
.....................  return -1
..............  return -1
.......  return false
 return false

 public synchronized boolean java.util.Vector.containsAll(java.util.Collection)
 arg1: this
.......  public java.util.Iterator java.util.AbstractList.iterator()
.......  return java.util.AbstractList$Itr@50269997
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 0
.......  return null
.......  public boolean java.util.Vector.contains(java.lang.Object)
.......  arg1: null
..............  public synchronized int java.util.Vector.indexOf(java.lang.Object,int)
..............  arg1: null
..............  arg2: 0
..............  return 0
.......  return true
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 1
.......  return TEST1
.......  public boolean java.util.Vector.contains(java.lang.Object)
.......  arg1: TEST1
..............  public synchronized int java.util.Vector.indexOf(java.lang.Object,int)
..............  arg1: TEST1
..............  arg2: 0
..............  return 1
.......  return true
.......  public synchronized int java.util.Vector.size()
.......  return 3
.......  public synchronized java.lang.Object java.util.Vector.get(int)
.......  arg1: 2
.......  return TEST2
.......  public boolean java.util.Vector.contains(java.lang.Object)
.......  arg1: TEST2
..............  public synchronized int java.util.Vector.indexOf(java.lang.Object,int)
..............  arg1: TEST2
..............  arg2: 0
..............  return 2
.......  return true
.......  public synchronized int java.util.Vector.size()
.......  return 3
 return true

 public synchronized int java.util.Vector.lastIndexOf(java.lang.Object)
 arg1: TEST
.......  public synchronized int java.util.Vector.lastIndexOf(java.lang.Object,int)
.......  arg1: TEST
.......  arg2: 2
.......  return -1
 return -1

JDKのProxyの代わりに使える

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

JDKのProxyの説明はこちら

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

サンプルコード

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

java cglibでmixin

リンク

Java with CGLIB でMixinを使う

http://d.hatena.ne.jp/iad_otomamay/20080512/p1

APIドキュメント

http://www.opendocs.net/javadoc/cglib/2.2/net/sf/cglib/proxy/Mixin.Generator.html

その他ネタ元はこちら

プログラマメモ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()で差込みを行うようです。

クラスをプロキシする

http://andore.com/money/trans/spring_ref_p7_ja.html#doc8_6.5.4

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

[Java]Javassistとcglibにおけるメソッドフィルタの指定

http://d.hatena.ne.jp/Kazzz/20080626/p1

cglibによるHogeクラスを拡張するEnhancerのフィルタとハンドラの指定

Hoge enhancedHoge = Enhancer.create(Hoge.class, null
       , new CallbackFilter(){
           @Override
           public int accept(Method method) {
               return (method != null && method.getName().equals("equals")
                       && method.getReturnType() == boolean.class
                       && method.getParameterTypes().length == 1
                       && method.getParameterTypes()[0] == Object.class)
                   ? 0 //NoOp.INSTANCEにマップする(捕捉しないということ)
                   : 1;
           }}
       , new Callback[]{NoOp.INSTANCE, new MethodInterceptor() {
           @Override
           public Object intercept(Object obj, Method method,
                   Object[] args, MethodProxy proxy) throws Throwable {
              System.out.println("*** before " + method.getName() + " ***");
              proxy.invoke(obj, args);
              System.out.println("*** after  " + method.getName() + " ***");
           }}}); 

ASMとは

ASM はJavaのバイトコードを操作したり、解析するためのフレームワークです。 既存のクラスを変更したり、動的にクラスを生成することができるフレームワークです。 CGLIBがこのASMをつかっています。

ASMのページ

http://asm.ow2.org/

日本語の使用例の記述

簡易AOPフレームワークの作成

http://d.hatena.ne.jp/iad_otomamay/20100418/1271560826

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-10-30 (土) 01:42:37 (4011d)