在学习Spring的时候接触到CGlib,是一个强大的Code生成类库!可以在运行期扩展Java接口,其底层是ASM框架。当然可以直接使用ASM,不过门槛较高。
首先来看JDK中原生的代理实现,首先实现InvocationHandler接口,相当于目标方法的代理:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------------------before------------------");
Object result = method.invoke(target, args); // 调用方法
System.out.println("-------------------after------------------");
return result;
}
然后创建代理对象:
Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), // 要实现的接口 this); // InvocationHandler的实现
JDK的代理最大的限制在于必须实现接口,而CGlib则并没有设置这个限制,而MethodInterceptor的用法与InvocationHandler几乎相同:
static class MyMethodInterceptor implements MethodInterceptor {
public Object intercept(Object targe, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("BEFORE");
Object result = methodProxy.invokeSuper(targe, args);
System.out.println("AFTER");
return result;
}
public Object createProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyService.class);
enhancer.setCallback(this);
return enhancer.create();
}
}
当Enhancer.setCallbacks设置了多个代理类怎么办呢?可以通过CallbackFilter来指定其执行顺序:
public interface CallbackFilter {
int accept(Method method);// 返回方法对应的下标
}
总感觉accept这个方法名起得太失败- -!再来看一个LazyLoader的例子,不明觉厉啊:
public class TestLazyLoader {
static class MyBean {
}
static class My {
MyBean myBean = (MyBean) Enhancer.create(MyBean.class, new MyLazy());
}
static class MyLazy implements LazyLoader {
public Object loadObject() throws Exception {
System.out.println("开始延迟加载!");
return new MyBean();
}
}
public static void main(String[] args) {
My my = new My();
System.out.println(my.myBean);// 如果没有这一句,就不会有任何输出
}
}
感觉有点不可思议,用什么方法才能拦截获取属性这个操作?再仔细想一下,应该不是这样!在System.out.println()执行的时候会调用对象的toString方法,而cglib做的仅仅是重写toString方法。赶紧用javap -verbose xxx来看生成的字节码,发现:
public final java.lang.String toString(); Code: Stack=2, Locals=1, Args_size=1 0: aload_0 1: dup 2: invokevirtual #30; //Method CGLIB$LOAD_PRIVATE_0:()Ljava/lang/Object; 5: invokevirtual #38; //Method java/lang/Object.toString:()Ljava/lang/String; 8: areturn
果然如此:D。
在Java中都是单继承的,当然CGlib也没办法打破这个限制,用多继承来描述Mixin貌似并不合适,但现在也没想到其他更好的:
public static void main(String[] args) {
Mixin mixin = Mixin.create(new Class[] { Inter1.class, Inter2.class },
new Object[] {
new Inter1() {
public void fun1(String arg0) {
System.out.println("Inter1 - " + arg0);
}
},
new Inter2() {
public void fun1(String arg0) {
System.out.println("Inter2 - " + arg0);
}
public void fun2(int arg0) {
System.out.println("Inter2 - " + arg0);
}
},
});
Inter1 inter1 = (Inter1) mixin;
inter1.fun1("hello");// Inter1 - hello
Inter2 inter2 = (Inter2) mixin;
inter2.fun1("world");// Inter1 - world
inter2.fun2(999);// Inter2 - 999
}
在多个类中有多个相同的方法时,总是前面的覆盖后面的,在底层具体的实现中应该是循环多次继承来实现多重继承的效果。
在业务代码中总会涉及到各种DO、BO、DTO等等,并需要经常在他们之间转化,CGlib提供了BeanCopier用来自动完成相同属性名称的映射:
static class A {
private int a = 1;
private int b = 2;
/** Setter And Getter */
}
static class B {
private int a = 3;
private int c = 4;
/** Setter And Getter */
}
public static void main(String[] args) {
BeanCopier beanCopier = BeanCopier.create(A.class, B.class, false);
A a = new A();
B b = new B();
beanCopier.copy(a, b, null);// 从a拷贝到b
System.out.println(a.a + " " + a.b); // 1 2
System.out.println(b.a + " " + b.c); // 1 4
}
当类型不匹配的时候需要用Converter进行转换:
public interface Converter {
/**
* @param value 源对象属性
* @param targetClass 目标对象属性类
* @param setterName 目标对象setter方法名
* @return 转换后的结果
*/
java.lang.Object convert(Object value, Class targetClass, Object setterName);
}
另外和Bean相关的还有其他几个类:
CGlib提供了FastClass来方便地进行反射操作。
这里有我写的一些测试DEMO。最后,其他操作字节码的工具还有:javassist,BCEL,ASM等。