深入瞭解java proxy代理
前段時間去阿里面試被問到 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看一下
貌似是一個名為[email protected]的一個類例項,並且裡面有一個InvocationHandler 的例項 h,h裡面有一個介面實現類的例項
[email protected],到這裡我們知道,動態代理是原理是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<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
往下看 suppelier.get()
@Override
public synchronized V get() { // serialize access
// re-check
Supplier<V> 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<V> 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<Void>() {
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方法的程式碼就明白了,哦 原來是這樣,搜嘎!