[[JAVAの記事一覧]]

&topicpath;
*目次 [#bca26b28]
#contents

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


*CGLIBとは? [#fa0ae660]

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

**CGLIBのWebページ [#xf7e883a]
http://cglib.sourceforge.net/

*本ページの趣旨 [#x0113801]
jparsecみてたら、動的ソースコード生成のライブラリであるcglibを使っており

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

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

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

*目次 [#bca26b28]
#contents
*インタフェース定義するだけでPOJO的なインスタンスを生成するサンプル [#q3790e79]
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));
      }
  }

*実行トレーサを作るサンプル [#p0328206]
http://cglib.sourceforge.net/xref/samples/Trace.html

*解説 [#t66723cd]
Enhancer クラスの使い方のサンプル


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

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

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

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

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


**サンプル [#o73635e1]
  
   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("  ");
    }
 }
**実行結果 [#j90f41b1]
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の代わりに使える [#a225fc5e]
なんのことかというと、あたかも元々のクラスのようにつかうことができるサンプルです。
**JDKのProxyの説明はこちら [#sccd2b29]
http://ffy.afy-system.jp/tips/t_004.html

**サンプルコード [#ffb5798e]
http://cglib.sourceforge.net/xref/samples/JdkCompatibleProxy.html

*java cglibでmixin [#cbef2da9]
**リンク [#q40a8740]
Java with CGLIB でMixinを使う

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


**APIドキュメント [#j2a36f06]
http://www.opendocs.net/javadoc/cglib/2.2/net/sf/cglib/proxy/Mixin.Generator.html


**その他ネタ元はこちら [#r59a6a77]

プログラマメモ2

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


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

***サンプルコード [#v588dc98]
 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;
    }
 }

**考察 [#i819e59c]
元にするクラスのインタフェースを用意しとかなきゃならんのかな?

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

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

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

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

元ネタサイトはこちら
**CGLIBにさわる [#k59f343f]
http://muimi.com/j/aop/cglib/

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

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

http://cglib.sourceforge.net/

Sampleの

Beansサンプルと思われる。

***掲載されていたサンプル [#h5c59501]
Foo.java

 package hoge;
 
 public class Foo {
 
    public void doSomething(){
        System.out.println("doSomethig");
    }
 }
Sample1.java
 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()で差込みを行うようです。

*クラスをプロキシする [#ka99d418]
http://andore.com/money/trans/spring_ref_p7_ja.html#doc8_6.5.4

*CGLIB で実行時にクラスにメソッドを追加する [#r95d066f]

URLはこちら

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

**サンプルコード [#k24425d2]
以下のサンプルでは、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;
    }
}

***実行結果 [#v266c17a]
 id : [999] createdAt : [Mon May 31 00:00:00 JST 2010]

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

**jmock [#q536b48c]
http://www.jmock.org/mocking-classes.html


*[Java]Javassistとcglibにおけるメソッドフィルタの指定 [#m8998281]
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とは [#m1a87732]
ASM はJavaのバイトコードを操作したり、解析するためのフレームワークです。
既存のクラスを変更したり、動的にクラスを生成することができるフレームワークです。
CGLIBがこのASMをつかっています。

**ASMのページ [#p3d1db5d]
http://asm.ow2.org/

**日本語の使用例の記述 [#fc30ec3f]
簡易AOPフレームワークの作成

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

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