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

サンプル

  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("  ");
   }
}

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()で差込みを行うようです。

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

ASMとは

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

ASMのページ

http://asm.ow2.org/

日本語の使用例の記述

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

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

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