WsztRush

CGlib

在学习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相关的还有其他几个类:

  1. BeanGenerator:根据Map动态生成BeanClass。
  2. BeanMap:将Bean转换成一个Map。
  3. BulkBean:更方便地操作属性。

CGlib提供了FastClass来方便地进行反射操作。

这里有我写的一些测试DEMO。最后,其他操作字节码的工具还有:javassist,BCEL,ASM等。