Top / cglibを使って動的コード生成
CGLIBとは、Javaコード生成ライブラリです。クラスファイルを実行時に読み込んで編集することが可能です(Javassistと同じジャンル)。CGLIBは、Hibernate、iBatisなどのDBアクセスツールや、Spring、SeasorなどのAOPコンテナなどで利用されています。
jparsecみてたら、動的ソースコード生成のライブラリであるcglibを使っており
興味が湧いたのでまとめてみる
ついでに、サンプルのコードの説明がなかったので、自分で説明を試みてみる。
SpringやらHibernateなんかに内部的に使われているライブラリーらしい。
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
なんのことかというと、あたかも元々のクラスのようにつかうことができるサンプルです。
http://ffy.afy-system.jp/tips/t_004.html
http://cglib.sourceforge.net/xref/samples/JdkCompatibleProxy.html
Java with CGLIB でMixinを使う
http://d.hatena.ne.jp/iad_otomamay/20080512/p1
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
元ネタサイトはこちら
メソッド実行の前後に処理をフックをかけるというか差し込む技がつかわれています。
元ネタの元ネタサイトは 本家
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
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]
インタフェースだけ定義しておいて、テスト用にモックオブジェクトを返すような開発ができるため、 分業することができる。
http://www.jmock.org/mocking-classes.html
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 はJavaのバイトコードを操作したり、解析するためのフレームワークです。 既存のクラスを変更したり、動的にクラスを生成することができるフレームワークです。 CGLIBがこのASMをつかっています。
簡易AOPフレームワークの作成