本文有如下三個主題:html
下面列出的JDK源碼中,隱藏了與分析無關的代碼,並不影響總體流程的理解。 如需查看所有源碼,請翻閱JDK源碼java
今天手寫了一個動態代理的代碼生成,感受不夠過癮,因而就對Proxy進行更深一層的探究。關於動態代理代碼生成,我將會在另外一篇文章中詳細講解,這裏主要來探究一下Proxy的原理和JDK的字節碼技術。數組
說到動態代理,不得不提到JDK中的Proxy類,它首先將業務類和InvocationHandler組裝,而後生成一個實現了業務類接口的代理對象。這個代理對象對業務類的方法進行攔截,能夠在業務方法前、後、異常等位置插入攔截代碼,也能夠對業務方法的參數、返回值進行修改,從而實現很是強大的代理功能。 Proxy的newProxyInstance原理 Proxy的核心方法就是newProxyInstance,具體源碼分析以下緩存
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
。。。。
//對接口數組進行復制
final Class<?>[] intfs = interfaces.clone();
。。。。
//核心中的核心,使用intfs建立一個代理對象,並使用loader進行加載,加載出來的類就是cl
Class<?> cl = getProxyClass0(loader, intfs);
try {
。。。
//獲取一個參數爲constructorParams的構造器,其中constructorParams定義爲:
//private static final Class<?>[] constructorParams = { InvocationHandler.class };
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
。。。。。。
//利用上面的有參構造器和傳入的參數h,建立一個實例對象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
//異常處理的代碼
}
}
複製代碼
在簡化的源碼,能夠看到 Proxy 生成一個代理類的主要步驟以下: 利用getProxyClass0建立一個代理類 獲取代理類的有參構造器,其中參數類型是:InvocationHandler.class 利用傳入的參數和有參構造器建立一個對象並返回,完成整個代理類的建立bash
再繼續探究下getProxyClass0是怎麼建立一個代理類的。oracle
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//若是接口的數量大於65535個,就會有異常
//通常業務中,不會有這麼多個接口,能夠放心的使用
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//大體意思:若是以前有緩存的Class就直接獲取,若是沒有,就使用ProxyClassFactory建立一個
// 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);
}
複製代碼
咱們研究的就是怎麼建立一個代理類的,那就看一下ProxyClassFactory。app
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
//代理類的名稱由兩部分組成 proxyClassNamePrefix+nextUniqueNumber,好比:$Proxy0
//代理類的前綴名
private static final String proxyClassNamePrefix = "$Proxy";
//代理類裏的編號
private static final AtomicLong nextUniqueNumber = new AtomicLong();
// 建立代理類的部分
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
//第一步 驗證接口
for (Class<?> intf : interfaces) {
//第1步 驗證全部的接口都是由loader加載的
//這裏由疑問的同窗,去了解一些類加載的機制和ClassLoader的命名空間
//我簡單講一下,同一個類被不一樣的ClassLoader加載出來的多個Class對象,在JVM中會被認爲是不一樣的類,沒法進行類型強轉
//好比說 類CA 實現了接口IA,巧合狀況下,類CA是由ClassLoaderA加載,接口IA由ClassLoaderB加載,若是ClassLoaderA和ClassLoaderB之間沒有委託關係,那麼CA是沒法強轉成IA的
//由於CA和IA是屬於不一樣的ClassLoader,歸屬於不一樣的命名空間,相互之間不可見
//這裏的驗證就是爲了不這個問題。
//通常這種類加載的驗證的流程都以下:
//1.使用給定的ClassLoader再次加載出一個臨時的Class對象
//2.和以前加載的Class對象比較
//3.若是兩個Class對象相等,就說明兩個類的ClassLoader是同一個,驗證經過
//4.不然就會拋出IllegalArgumentException異常
//有興趣的同窗能夠去分析一下JDBC的類加載流程,也有相似的類加載驗證
Class<?> interfaceClass = null;
try {
//使用給定的loader再次加載一次
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
//和傳入的接口類對比
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
//第2步
//驗證是否是真正的接口
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
//第3步
//驗證是否是重複的接口,不重複就把接口放到interfaceSet中
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
//第二步 肯定代理類的包名
//若是接口 全是public 的話,報名就是ReflectUtil.PROXY_PACKAGE,即com.sun.proxy
//若是接口 有內部接口 的話,就用內部接口的外部類的包名
//若是內部接口來自不一樣類的話,就不能生成代理類,直接拋出異常返回
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
//驗證全部接口是否是所有public 或者 都來自同一個包中
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
//使用默認的com.sun.proxy做爲包名
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
//第三步 肯定代理類的類名
//使用上面的包名和生成的類名組成一個全限定名
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//第四步 生成代理類的字節碼
//這裏生成代理類使用了不開源的ProxyGenerator實現,下面會進行分析
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
//第五步 利用字節碼建立一個Class對象
//這裏使用了本地方法defineClass0 來定義一個類,裏面的原理再也不進行分析,有興趣的同窗,能夠
//搜索相關的資料
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
複製代碼
終於到了最激動人心的地方,JDK動態代理技術的核心,字節碼生成技術! 雖然這裏是不開源的,可是經過反編譯的代碼仍是能夠大體的學習一下。jvm
public static byte[] generateProxyClass(final String proxyName, Class<?>[] interfaces, int accessFlags) {
//建立一個ProxyGenerator
ProxyGenerator proxyGenerator = new ProxyGenerator(proxyName, interfaces, accessFlags);
//生成字節碼 ,字節碼技術核心方法
final byte[] bytes = proxyGenerator.generateClassFile();
//是否存儲生成的字節碼到class文件中,這塊不進行分析了,有興趣的同窗自行分析下
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int interfaces = proxyName.lastIndexOf(46);//46就是 ASCII中的 .
Path accessFlags;
if (interfaces > 0) {
Path path = Paths.get(proxyName.substring(0, interfaces).replace('.', File.separatorChar));
Files.createDirectories(path);
accessFlags = path.resolve(proxyName.substring(interfaces + 1, proxyName.length()) + ".class");
} else {
accessFlags = Paths.get(proxyName + ".class");
}
Files.write(accessFlags, bytes, new OpenOption[0]);
return null;
} catch (IOException e) {
throw new InternalError("I/O exception saving generated file: " + e);
}
}
});
}
return bytes;
}
複製代碼
注意,若是對於JVM規範中Class文件結構不瞭解的同窗,請不要深究下面的代碼分析。能夠先去了解一下JVM中的Class文件規範 Class文件格式參考地址: docs.oracle.com/javase/spec…ide
反編譯的代碼,基本能夠看懂整個流程函數
private byte[] generateClassFile() {
//生成3個基本方法-和代理方法的字節碼
this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);
//獲取全部接口中全部的方法,生成方法字節碼
Class[] interfaces = this.interfaces;
int len = interfaces.length;
int i;
Class clazz;
for (i = 0; i < len; ++i) {
clazz = interfaces[i];
Method[] methods = clazz.getMethods();
int methodCount = methods.length;
for (int j = 0; j < methodCount; ++j) {
Method m = methods[j];
this.addProxyMethod(m, clazz);
}
}
Iterator var11 = this.proxyMethods.values().iterator();
//檢查返回值,其實是檢查方法的重載,是否是符合java規範
//兩個同名方法若是參數類型一致,返回值不一樣被認爲是不合法的
List var12;
while (var11.hasNext()) {
var12 = (List) var11.next();
checkReturnTypes(var12);
}
Iterator var15;
try {
//添加構造函數的字節碼
this.methods.add(this.generateConstructor());
var11 = this.proxyMethods.values().iterator();
while (var11.hasNext()) {
var12 = (List) var11.next();
var15 = var12.iterator();
while (var15.hasNext()) {
ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod) var15.next();
this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
this.methods.add(var16.generateMethod());
}
}
//生成靜態代碼塊字節碼
this.methods.add(this.generateStaticInitializer());
} catch (IOException var10) {
throw new InternalError("unexpected I/O Exception", var10);
}
if (this.methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
} else if (this.fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
} else {
//常量池添加元素
this.cp.getClass(dotToSlash(this.className));
this.cp.getClass("java/lang/reflect/Proxy");
var1 = this.interfaces;
var2 = var1.length;
for (var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
this.cp.getClass(dotToSlash(var4.getName()));
}
this.cp.setReadOnly();
//準備拼湊Class文件
ByteArrayOutputStream var13 = new ByteArrayOutputStream();
DataOutputStream var14 = new DataOutputStream(var13);
try {
//寫入魔術CAFEBABE
var14.writeInt(-889275714);
//寫入版本號
var14.writeShort(0);
var14.writeShort(49);
//寫入常量池
this.cp.write(var14);
//寫入類訪問標誌
var14.writeShort(this.accessFlags);
//寫入本類類名
var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
//寫入父類類名
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
//寫入接口的數量
var14.writeShort(this.interfaces.length);
//寫入接口的字節碼
Class[] var17 = this.interfaces;
int var18 = var17.length;
for (int var19 = 0; var19 < var18; ++var19) {
Class var22 = var17[var19];
var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
}
//寫入屬性字段的個數
var14.writeShort(this.fields.size());
var15 = this.fields.iterator();
//寫入全部的屬性字段
while (var15.hasNext()) {
ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo) var15.next();
var20.write(var14);
}
//寫入方法個數
var14.writeShort(this.methods.size());
var15 = this.methods.iterator();
//寫入所有方法
while (var15.hasNext()) {
ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo) var15.next();
var21.write(var14);
}
//寫入其餘附加屬性個數,這裏沒有附加屬性
var14.writeShort(0);
//將生成的Class字節碼轉成byte[] 返回
return var13.toByteArray();
} catch (IOException var9) {
throw new InternalError("unexpected I/O Exception", var9);
}
}
}
複製代碼
看到這裏,相信不少人仍是一臉懵,不過JDK中提供了工具,能夠將生成的字節碼輸出成Class文件,咱們在利用反編譯工具,就能夠查看動態生成的代碼究竟是什麼樣子的。
工具類代碼
/** * @author wangpp */
public class ProxyUtils {
/** * 獲取代理類對象的文件 * * @param className * @param fileName * @param interfaces * @return */
public static File getProxyClassFile(String className, String fileName, Class<?>[] interfaces) {
byte[] bytes = ProxyGenerator.generateProxyClass(className, interfaces);
File file = new File(fileName);
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(bytes);
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
public static void main(String[] args) {
File h = getProxyClassFile("h", "h.class", new Class[]{IA.class});
System.out.println(h);
}
}
複製代碼
接口類IA代碼
/** * @author wangpp */
public interface IA {
String m1(String a, String b);
void m2(String a, String b) throws Exception;
}
複製代碼
編譯出的Class文件:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.edfeff.proxy.jdk.demo.IA;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class h extends Proxy implements IA {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
public h(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 String m1(String var1, String var2) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
public final void m2(String var1, String var2) throws Exception {
try {
super.h.invoke(this, m4, new Object[]{var1, var2});
} catch (Exception | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
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("com.edfeff.proxy.jdk.demo.IA").getMethod("m1", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
m4 = Class.forName("com.edfeff.proxy.jdk.demo.IA").getMethod("m2", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
複製代碼
能夠看到生成的代理類,幫咱們代理了類的全部方法,而且方法的執行模式都是一致的,分析以下:
try {
//調用invocationHandler的invoke方法,將代理對象(本身),攔截的方法、方法參數,傳入
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
複製代碼
看到這裏,相信你們已經明白了動態代理的原理了。其中核心的類就是Proxy、InvocationHandler和ProxyGenerator。 Proxy供外部訪問、管理校驗、流程管理、生成代理類的加載、代理類緩存、代理對象的實例化 InvocationHandler 負責融合你們的業務代碼 ProxyGenerator 負責組裝Class字節碼 關於ProxyGenerator中字節碼的生成部分,有機會給你們繼續解析。
Class文件中規定了存儲Interface數量的大小爲2個字節,即16位
第1、這個數字已經足夠大了,足以知足任意種業務狀況,幾乎沒有任何一個正常程序的接口數量會達到這個值 第2、這個要問下高司令了(狗頭)