Top / cglibを使って動的コード生成
jparsecみてたら、動的ソースコード生成のライブラリ使っており興味が湧いたのでまとめてみる
SpringやらHibernateなんかに内部的に使われているライブラリーらしい。
http://cglib.sourceforge.net/xref/samples/KeySample.html
http://cglib.sourceforge.net/xref/samples/Trace.html
なんのことかというと、あたかも元々のクラスのようにつかうことができるサンプルです。
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()で差込みを行うようです。
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]
インタフェースだけ定義しておいて、テスト用にモックオブジェクトを返すような開発ができるため、 分業することができる。