这里其实是有面试题的,我之前还被问过,问我用的什么技术来生成class字节流,这里其实是没有用任何第三方工具的,这个类的import语句部分,也没有asm、javaasist等工具。
import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import sun.security.action.GetBooleanAction;
加载class字节流为Class
这部分的代码即为前面提到的:
try { proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); }其中,defineClass0 是一个native方法:
java.lang.reflect.Proxy#defineClass0 private static native Class defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);让我比较惊讶的是,这个native方法,是在Proxy类里,且除了此处的调用,没有被其他代码调用。
我去看了Classloader这个类的代码,里面也有几个native的defineClass的方法:
private native Class defineClass0(String name, byte[] b, int off, int len, ProtectionDomain pd); private native Class defineClass1(String name, byte[] b, int off, int len, ProtectionDomain pd, String source); private native Class defineClass2(String name, java.nio.ByteBuffer b, int off, int len, ProtectionDomain pd, String source);看来,Proxy是自己自立门户啊,没有使用Classloader类下面的defineClass等方法。
如果大家想看生成的class的文件的内容,可以加这个虚拟机启动参数:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
或者main最前面,加这个:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
反射生成代理对象
这步就没啥好说的了,经过上面第二步,已经拿到Class对象了。反射对于大家,也是轻车熟路了。
Constructor<?> cons = cl.getConstructor({ InvocationHandler.class }); final InvocationHandler ih = h; newInstance(cons, ih); private static Object newInstance(Constructor<?> cons, InvocationHandler h) { return cons.newInstance(new Object[] {h} ); }这里,我们看到,获取的构造函数,就是要接收一个InvocationHandler对象的。拿到了构造函数后,接下来,就调用了构造函数的newInstance,来生成代理对象。
具体的调用就不说了,反正你调用的任何方法(只能调用接口里有的那些),都会转到invocationHandler的invoke方法。
关于jdk动态代理的思考其实,大家看到上面,会想下面这个问题不?现在在代理对象上,调用方法,最终都会进入到:
java.lang.reflect.InvocationHandler#invoke public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;我如果想在这个逻辑里面,去调用原始目标的方法,怎么办呢?
我们看看传给我们的几个参数:
1. proxy,代理对象;这个没办法拿到原始对象 2. method,是被调用的方法,也拿不到原始对象 3. args,给method的参数,也拿不到原始对象。这就迷离了。那我咋办呢?
答案是,在创建InvocationHandler时,把原始对象传进去,以及其他一切必要的信息,都传进去。
当然,你也可以不传进去,在invoke方法里,为所欲为,比如下面的方法:
ClassLoader loader = Thread.currentThread().getContextClassLoader(); Object generatedProxy = Proxy.newProxyInstance(loader, new Class[]{Perform.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("到我这为止,不会调用target了"); return null; } }); // 这里,虽然调用了sing,但里面的逻辑也不会执行。 ((Perform)generatedProxy).sing();其实,这个代理,已经相当于是Perform接口的另一个实现了;和之前的实现类,没有半毛钱关系。