前段时间去阿里面试被问到 java proxy 感觉自己回答的不是很理想,所以打算通过查看jdk源码深入的学习一下java 动态代理;
上代码:
先写一个接口ProxyTest:
public interface ProxyTest {
void test1();
}
写一个实现类ProxyTestImpl:
public class ProxyTestImpl implements ProxyTest {
@Override
public void test1() {
System.out.println("方法test执行");
}
}
写一个main方法实现代理:
public static void main(String[] args) {
ProxyTest proxyTest = new ProxyTestImpl();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("test1")) {
System.out.println("执行方法前");
method.invoke(proxyTest, args);
System.out.println("执行方法后");
}
return null;
}
};
ProxyTest proxy1 = (ProxyTest) Proxy.newProxyInstance(TEst.class.getClassLoader(), proxyTest.getClass().getInterfaces(), h);
proxy1.test1();
}
通过上面代码可以看到在调用方法前和行方法后执行了自己的代码,打印出一段提示文字
执行结果:
很简单的一段动态代理代码,但是请思考一个问题,最终代理得到的proxy1是什么?debug看一下
貌似是一个名为$proxy0@649的一个类实例,并且里面有一个InvocationHandler 的实例 h,h里面有一个接口实现类的实例
ProxyTestImpl@647,到这里我们知道,动态代理是原理是Proxy为我们生成了一个代理类,但是这个类内容是什么?debug看不出来,那我们就继续往下看找这个生成的类存到了哪里(磁盘还是内存)?
查看 Proxy.newProxyInstance 方法源码关键一句:
/*
* Look up or generate the designated proxy class.
*/
Class> cl = getProxyClass0(loader, intfs);
很明显这就是生成类的方法,继续往下看 getProxyClass0 方法 :
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class> getProxyClass0(ClassLoader loader,
Class>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
继续往下 看 proxyClassCache.get(loader, interfaces);
这个方法代码有点多就不贴了,意思就是先要去缓存weakCache中找一下,如果之前生成过这个代理类就会直接返回,否则才去重新生成代理类,并且放入缓存;贴几句关键代码:
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue instance
V value = supplier.get();
if (value != null) {
return value;
}
}
往下看 suppelier.get()
@Override
public synchronized V get() { // serialize access
// re-check
Supplier supplier = valuesMap.get(subKey);
if (supplier != this) {
// something changed while we were waiting:
// might be that we were replaced by a CacheValue
// or were removed because of failure ->
// return null to signal WeakCache.get() to retry
// the loop
return null;
}
// else still us (supplier == this)
// create new value
V value = null;
try {
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// the only path to reach here is with non-null value
assert value != null;
// wrap value with CacheValue (WeakReference)
CacheValue cacheValue = new CacheValue<>(value);
// try replacing us with CacheValue (this should always succeed)
if (valuesMap.replace(subKey, this, cacheValue)) {
// put also in reverseMap
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
// successfully replaced us with new CacheValue -> return the value
// wrapped by it
return value;
}
}
关键的一句代码 valueFactory.apply(key, parameter) 继续向下跟
apply方法中关键几句代码:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
终于看到反回字节码的方法了,,,就是 ProxyGenerator.generateProxyClass,快进去看看!
public static byte[] generateProxyClass(final String var0, Class>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
return var4;
}
查看这个方法代码可以知道如果saveGeneratedFiles值为false,生产字节码就直接返回去了,如果值为true就会生成class文件,那我想看class文件就得把这个值设置为true,看一下这个值在哪里定义
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
可以看到,这个值是去读的系统变量 sun.misc.ProxyGenerator.saveGeneratedFiles,那我就去把这个变量设置一下,就在main方法的最前面添加一句代码:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
然后debug运行,为了拿到class存放路径,就又在Files.write(var2, var4, new OpenOption[0]);往下跟了几个方法,大概调用关系如下:
一直到newByteChannel方法
终于找到代理类路径了,现在就去看一下这个类里到底有啥!代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements ProxyTest {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void test1() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("ProxyTest").getMethod("test1");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
关键两句代码:
super.h.invoke(this, m3, (Object[])null);
m3 = Class.forName("ProxyTest").getMethod("test1");
可以看到,test1方法调用的是 InvocationHandler.invoke 方法 传入的第二个参数是接口ProxyTest的方法test1;该接口实现类是ProxyTestImpl,所以真正调用的是 ProxyTestImpl 的 test1 方法;再回头看main方法的代码就明白了,哦 原来是这样,搜嘎!