曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory) (3)

这里其实是有面试题的,我之前还被问过,问我用的什么技术来生成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接口的另一个实现了;和之前的实现类,没有半毛钱关系。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywjjf.html