动态代理的东东 听起来很牛,用起别人提供的现成的工具,也很方便,比如spring 的事务管理,虽然我们用的是和不是很多,但是真正用的时候,会有一些意想不到的东东出现,本着知其然并知其所以然的原则,我们来看下到底是怎么实现的
下面会研究下 java本身实现的动态代理,cglib实现的动态代理, 其他的比如bcel和javassist实现的,大家可以自己研究下,大同小异
好了,我们开始,本着诚实的原则(第二个原则) ,下面的内容错了的都是我的,对的都是自立的J。
首先看下两个实现是怎么写的,上代码(很烦,但是很必要),由于java是需要接口的,因此我们会有一个接口,同时会有实现,模拟一个简单的计时的动态代理
接口:public interface IHello { public String sayHello(String ts);}
实现:public class HelloWorld implements IHello{ public String sayHello(String ts) { return "hello "+ts; }}
Java的实现
public class DynaHello implements InvocationHandler {
private Object obj;
public DynaHello(Object obj) { this.obj = obj;}
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
long start = System.currentTimeMillis();
Object o = method.invoke(obj, args);
System.out.println("Call to sayHello took "+ (System.currentTimeMillis() - start) + " ms.");
return o;
}
}
Cglib的实现
public class cglibProxy implements MethodInterceptor {
private Enhancer enhancer=new Enhancer();
public Object getProxyInstance(Class clz){
enhancer.setSuperclass(clz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long start = System.currentTimeMillis();
Object o = proxy.invokeSuper(obj, args);
System.out.println("Call to sayHello took " + (System.currentTimeMillis() - start) + " ms.");
return o;
}
}
测试java 动态代理类
public class TestJavaDyna {
public static void main(String[] args) {
HelloWorld hw = new HelloWorld();
DynaHello dh = new DynaHello(hw);
IHello hello = (IHello) Proxy.newProxyInstance(hw.getClass().getClassLoader(), hw.getClass().getInterfaces(), dh);
hello.sayHello("hello world");
}
}
下面我们来看下java是怎么实现动态代理的
首先来看Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
很简单,除去异常的处理,就三句话
Class cl = getProxyClass(loader, interfaces);//@1生成一个class
Constructor cons = cl.getConstructor(constructorParams);//@2得到这个class的构造函数
return (Object) cons.newInstance(new Object[] { h });//@3用这个构造函数,和构造函数的参数 构造一个对象 然后返回
其中constructorParams ={ InvocationHandler.class }; h就是传入的InvocationHandler的实现,这里面会在生成字节码的时候,设置进去自己的父类 java/lang/reflect/Proxy,父类有一个有参构造函数比较重要
protected Proxy(InvocationHandler h) { this.h = h; } 。
下面的这幅图也许比较清晰
从上面可以看到主要还是生成代理类字节码,详细的就不讲了,举个例子讲下怎么生成对应的代理方法的,下面的就是方法生成中用到的代码
//1、这几句字节码的意思得到父类的一个属性,类型为InvocationHandler,此时superclassName = "java/lang/reflect/Proxy",handlerFieldName = "h";
out.writeByte(opc_getfield); out.writeShort(cp.getFieldRef( superclassName, handlerFieldName, "Ljava/lang/reflect/InvocationHandler;"));
//2、这句话对应的字节码就是aload_0,也就是this的引用
code_aload(0, out);
//3、这几句是得到静态属性,类型为Method,className= proxyPkg + $Proxy + num, proxyPkg是包名,因为接口的类型可以是public或者默认,public的时候proxyPkg就是空,默认的话,就需要代理类和接口在一个包内,包可见。
// num是自增计数器,methodFieldName就是代理类中的属性的name
out.writeByte(opc_getstatic); out.writeShort(cp.getFieldRef(dotToSlash(className),methodFieldName, "Ljava/lang/reflect/Method;"));
//4、下面是对应方法中的参数,如果没有则是空null
if (parameterTypes.length > 0) { code_ipush(parameterTypes.length, out);out.writeByte(opc_anewarray); out.writeShort(cp.getClass("java/lang/Object"));
for (int i = 0; i < parameterTypes.length; i++) {
out.writeByte(opc_dup);
code_ipush(i, out);
codeWrapArgument(parameterTypes[i], parameterSlot[i], out);
out.writeByte(opc_aastore);
}
} else {
out.writeByte(opc_aconst_null);
}
//5、下面两句话是调用接口,第一个参数为上面load的this,第二个参数为对应Method类型的方法,第三个参数就是上面组装的方法中的参数
out.writeByte(opc_invokeinterface);
out.writeShort(cp.getInterfaceMethodRef("java/lang/reflect/InvocationHandler","invoke","(Ljava/lang/Object;Ljava/lang/reflect/Method;" +"[Ljava/lang/Object;)Ljava/lang/Object;"))
这个太枯燥了,下面我们用jd_gui看下生成好的字节码是什么样的,挑个sayHello方法吧,除去各种异常处理,方法如下:
public final String sayHello(){ return ((String)this.h.invoke(this, m3, new Object[] { paramString })); }
其中h就是父类Proxy中的属性,m3 = Class.forName("javaDyna.IHello").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });也就是m3是接口中的方法
这个里面有个地方 sayhello没有参数了,这个是jd的问题,请大家不要纠结在这个地方。
通过这一个方法,就可以知道,java动态代理是将所有的方法都委托给了InvocationHandler实现中的invoke方法
了,
看着很简单,呵呵,但是这里面的一些异常,以及缓存的处理还是值得学些的,当然我们只看主干,就行了哈哈
上面就将生成字节码说完了,最后通过 将InvocationHandler的实现传入构造函数,就完成了代理对象的生成,
那时的h才真正是实现。