[[JAVAの記事一覧]] &topicpath; *目次 [#bca26b28] #contents *CGLIBとは? [#fa0ae660] CGLIBとは、Javaコード生成ライブラリです。クラスファイルを実行時に読み込んで編集することが可能です(Javassistと同じジャンル)。CGLIBは、Hibernate、iBatisなどのDBアクセスツールや、Spring、SeasorなどのAOPコンテナなどで利用されています。 **CGLIBのWebページ [#xf7e883a] http://cglib.sourceforge.net/ *本ページの趣旨 [#x0113801] jparsecみてたら、動的ソースコード生成のライブラリであるcglibを使っており 興味が湧いたのでまとめてみる ついでに、サンプルのコードの説明がなかったので、自分で説明を試みてみる。 SpringやらHibernateなんかに内部的に使われているライブラリーらしい。 *インタフェース定義するだけで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 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 で実行時にクラスにメソッドを追加する [#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 *ASMとは [#m1a87732] ASM はJavaのバイトコードを操作したり、解析するためのフレームワークです。 既存のクラスを変更したり、動的にクラスを生成することができるフレームワークです。 CGLIBがこのASMをつかっています。 **ASMのページ [#p3d1db5d] http://asm.ow2.org/ **日本語の使用例の記述 [#fc30ec3f] 簡易AOPフレームワークの作成 http://d.hatena.ne.jp/iad_otomamay/20100418/1271560826