本系列文章主要是博主在學習spring aop的過程中瞭解到其使用了java動態代理,本着究根問底的態度,於是對java動態代理的本質原理做了一些研究,於是便有了這篇文章
爲了儘快進入正題,這裏先跳過spring aop和java動態代理的使用流程的講解
不過,我們首先還是先看下java dynamic proxy的基本使用方法,假定我們要代理的對象是一個Map,則代碼如下:
Map proxyInstance = (Map) Proxy.newProxyInstance(
HashMap.class.getClassLoader(),
new Class[]{Map.class},
new DynamicInvocationHandler());
之後proxyInstance就可以作爲一個正常的Map對象進行使用了
爲了對生成對象的屬性做一個基本的瞭解,我們先打印一下proxyInstance的實際類型名稱
System.out.println(proxyInstance.getClass().getName());
得到結果
com.sun.proxy.$Proxy11
如果使用多了,就會發現所有的代理類的名稱都是$Proxy加一個數字,且包名是com.sun.proxy
當我們查看Proxy.newProxyInstance方法時,會發現它返回的其實是一個Object對象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
而在實際使用的過程中,它是可以被直接轉型成我們傳入的接口類型,因此可以推測出,該proxyInstance對象的實際類型肯定是實現了我們傳入的接口
我們打印一下該類實現的接口
for (Class intf : proxyInstance.getClass().getInterfaces()) {
System.out.println(intf.getName());
}
得到結果
java.util.Map
符合我們之前的推測
接着我們再打印一下該類的父類
System.out.println(proxyInstance.getClass().getSuperclass().getName());
得到結果
java.lang.reflect.Proxy
因此總結一下,該proxyInstance對象有以下3個屬性
1.繼承了Proxy類
2.實現了我們傳入的接口
3.以$Proxy+隨機數字的命名
那麼動態生成代理類的功能究竟是如何實現的呢?接下去就來看java的源碼
因爲源碼有點多,所以我只貼出關鍵的部分
入口自然是Proxy.newProxyInstance方法
其中有2個部分我們需要關心
第一部分,類的創建
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
這個就是實際生成類的方法,後面我們會繼續深究,先略放一放
第二部分,實例的創建
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
...
return cons.newInstance(new Object[]{h});
最終對象的實例化過程就是通過之前生成的class,獲取其指定參數的構造函數,並將InvocationHandler對象傳入
查看constructorParams字段
/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
的確就是獲取InvocationHandler對象的一個構造函數
回想一下之前類定義的第一條,繼承了Proxy類,因此我們去Proxy類中找一下
/**
* Constructs a new {@code Proxy} instance from a subclass
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
* @param h the invocation handler for this proxy instance
*
* @throws NullPointerException if the given invocation handler, {@code h},
* is {@code null}.
*/
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
在該構造函數中就是將參數h賦值給了成員變量h,這裏名稱h可以記一下,在之後的文章中還會遇到
看完實例的創建,讓我們回到更重要的第一部分,類的生成
進入getProxyClass0(loader, intfs)方法
/**
* 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);
}
該方法很簡單,直接從一個cache中拿取對象
查看proxyClassCache對象
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
該對象本質就是一個類似於Map的緩存,不過使用的是WeakCache,這個WeakCache本身的特性我們放到另一篇文章中討論,本文專注於Proxy
我們可以看到該緩存的構造函數獲取了2個Factory,顧名思義,第一個是生成key的,第二個是生成ProxyClass的,自然我們需要繼續看第二個Factory
類的註解如下
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
這個就是我們要尋找的負責具體生成類的工廠了,查看其apply方法
首先其會對傳入的接口類型做一些校驗,包括loader能否加載到傳入的接口,接口是否實際上是接口(因爲數組的類型是Class),接口是否有重複
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
接着設置類的默認access_flag,public final
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
接着檢查傳入的接口數組中是否包含非public的接口,如果有,則生成的類需要和該接口處於同一個package,且訪問屬性會去掉public,只保留final。如果有多個不同package中的非public接口,則報錯
(具體原因大家應該都可以理解)
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
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");
}
}
}
如果沒有非public類,則會使用默認的package名,即com.sun.proxy
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
然後獲取一個靜態自增的int
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
固定的類名前綴
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
將上面三者組合成最終的類名(回想之前我們打印出的實例的類名)
String proxyName = proxyPkg + proxyClassNamePrefix + num;
上面這幾個步驟確定了類的名稱,但還是皮毛,接下去是生成類的血肉:字節碼
/*
* Generate the specified proxy class.
*/
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());
}
而該方法是一個native的方法,所以暫時就無法繼續探究了,不過知道了這個方法後,如果我們自己有需要,也可以利用這種機制實現自己的動態類生成,後面會想辦法做一個demo,本文就不做探討了
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
之前其實都是開胃菜,現在回到之前生成字節碼的方法,查看方法源碼
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;
}
中間if部分的代碼可以先忽略,不過我們會在後面的文章中使用到這部分功能,這裏先關注下面這2行代碼
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
這裏讓我們記一下
var0是類名
var1是接口
var3是access_flag
後面我會盡量將這些varX轉換成更實際的命名,方便大家理解
之後就是本文的最終的重點,也是難點,即二進制字節碼的實際生成過程,包括jvm操作指令,所以我們需要先對class文件的結構和jvm操作指令有一個瞭解
jvm文檔地址:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
下面對字節碼的結構簡單地做了個說明,大部分都是顧名思義
ClassFile {
u4 magic;//固定的開頭,值爲0xCAFEBABE
u2 minor_version;//版本號,用來標記class的版本
u2 major_version;//版本號,用來標記class的版本
u2 constant_pool_count;//靜態池大小,是靜態池對象數量+1
cp_info constant_pool[constant_pool_count-1];//靜態池對象,有效索引是1 ~ count-1
u2 access_flags;//public、final等描述
u2 this_class;//當前類的信息
u2 super_class;//父類的信息
u2 interfaces_count;//接口數量
u2 interfaces[interfaces_count];//接口對象
u2 fields_count;//字段數量
field_info fields[fields_count];//字段對象
u2 methods_count;//方法數量
method_info methods[methods_count];//方法對象
u2 attributes_count;//屬性數量
attribute_info attributes[attributes_count];//屬性對象
}
爲了不成爲一篇枯燥的文檔翻譯,並且儘快進入Proxy的源碼,這裏並不會對每一個部分做特別詳細的說明,以把握整體爲主
接下去我們就可以進入generateClassFile()方法了
首先把握整體,我們先跳過一部分細節代碼,先看下面這部分(這裏我做了一個可讀性的變量名修改)
注意對照着Class的字節結構來看
最終輸出的字節流
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream data = new DataOutputStream(byteStream);
寫入固定開頭magic,這裏-889275714就是對應0xCAFEBABE
data.writeInt(-889275714);
寫入版本號
data.writeShort(0);//minor_version
data.writeShort(49);//major_version
寫入常量池,這裏cp就是指constant pool
this.cp.write(data);
這裏我們需要進入cp的write方法看一下,也先不要糾結Entry的細節,我們還是先把握整體
public void write(OutputStream var1) throws IOException {
DataOutputStream var2 = new DataOutputStream(var1);
/**
* 這裏寫入cp的大小,注意size()+1,可以和之前Class結構中的constant_pool_count對應
*/
var2.writeShort(this.pool.size() + 1);
Iterator var3 = this.pool.iterator();
/**
* 遍歷cp中的對象,寫入詳細信息,對應Class結構中的cp_info
*/
while(var3.hasNext()) {
ProxyGenerator.ConstantPool.Entry var4 = (ProxyGenerator.ConstantPool.Entry)var3.next();
var4.write(var2);
}
}
接着我們回到外層方法,繼續往下看
寫入access_flag
data.writeShort(this.accessFlags);
寫入當前類的信息
data.writeShort.writeShort(this.cp.getClass(dotToSlash(this.className)));
寫入父類的信息(回想類的屬性第一條,繼承了Proxy類)
data.writeShort.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
寫入接口數量
data.writeShort.writeShort(this.interfaces.length);
遍歷接口,寫入接口信息
Class[] interfaces = this.interfaces;
int interfaceLength = interfaces.length;
for (int i = 0; i < interfaceLength; ++i) {
Class intf = interfaces[i];
data.writeShort(this.cp.getClass(dotToSlash(intf.getName())));
}
寫入字段數量
data.writeShort(this.fields.size());
遍歷字段,寫入字段信息
fieldInerator = this.fields.iterator();
while(fieldInerator.hasNext()) {
ProxyGenerator.FieldInfo fieldInfo = (ProxyGenerator.FieldInfo) fieldInerator.next();
fieldInfo.write(data);
}
寫入方法數量
data.writeShort(this.methods.size());
遍歷方法,寫入方法信息
methodIterator = this.methods.iterator();
while(methodIterator.hasNext()) {
ProxyGenerator.MethodInfo methodInfo = (ProxyGenerator.MethodInfo) methodIterator.next();
methodInfo.write(data);
}
因爲該類沒有特別的attribute,因此attribute數量直接寫0
data.writeShort(0);
正和之前的類結構完全一一對應,此時我們對proxy所做的事情就有了一個整體的把握
瞭解了整體之後,下面再深入介紹一下字節碼中部分對象的具體格式,爲後面進一步看Proxy的源碼做一些準備
爲了更好地理解下面的內容,我們先定義一個簡單的類Test.java
public class Test implements TestInt {
private int field = 1;
public int add(int a, int b) {
return a + b;
}
}
interface TestInt {
}
生成.class文件
javac Test.java
查看.class文件
javap -v Test.class
得到結果
Classfile /Users/tianjiyuan/Documents/jvm/Test.class
Last modified 2020-7-3; size 292 bytes
MD5 checksum 1afecf9ea44088238bc8aa9804b28208
Compiled from "Test.java"
public class Test implements TestInt
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#16 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#17 // Test.field:I
#3 = Class #18 // Test
#4 = Class #19 // java/lang/Object
#5 = Class #20 // TestInt
#6 = Utf8 field
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 add
#13 = Utf8 (II)I
#14 = Utf8 SourceFile
#15 = Utf8 Test.java
#16 = NameAndType #8:#9 // "<init>":()V
#17 = NameAndType #6:#7 // field:I
#18 = Utf8 Test
#19 = Utf8 java/lang/Object
#20 = Utf8 TestInt
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field field:I
9: return
LineNumberTable:
line 1: 0
line 2: 4
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 5: 0
}
SourceFile: "Test.java"
我們先看下面這3個部分正對應minor_version,major_version,access_flags
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
接着看Constant Pool
Constant pool:
#1 = Methodref #4.#16 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#17 // Test.field:I
#3 = Class #18 // Test
...
#6 = Utf8 field
...
#16 = NameAndType #8:#9 // "<init>":()V
其中有如下幾種類型
Methodref :方法的引用
Fieldref:字段的引用
Class :類的引用
Utf8 :字符串的引用
NameAndType 類型的描述
下面依據jvm文檔,一個一個介紹
Class結構
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
表示一個類的引用
tag:表示自身在常量池中的索引
name_index:必須是常量池中的有效索引,用來表示類的名字
例如
#3 = Class #18 // Test
tag = 3,表示自身索引爲3
name_index = 18,表示名字的索引是18
此時我們查看#18,即這個類的名字是Test
#18 = Utf8 Test
Field、Method、Interface結構
文檔中這3者是放在一起的
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
表示一個字段、方法、接口方法的引用
tag:表示自身在常量池中的索引
class_index:表示常量池中的一個有效索引
如果是Methodref_info必須是Class類型的
如果是InterfaceMethodref_info則必須是一個Interface
如果是Fieldref_info則可以是Class或者是Interface
name_and_type_index:表示常量池中的一個有效索引(表示方法的名字、返回類型、參數)
如果是Fieldref_info,則必須是一個對字段的描述,否則必須是一個對方法的描述
例如
#1 = Methodref #4.#16 // java/lang/Object."<init>":()V
tag = 1,表示自身索引爲1
class_index = 4,表示類型是索引爲4的類
name_and_type_index = 16,表示方法的描述爲索引16
查看4和16
#4 = Class #19 // java/lang/Object
#16 = NameAndType #8:#9 // "<init>":()V
即表示這個方法是Object類中的構造函數
NameAndType結構
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
用來表示一個方法或者字段,其中不包括該字段或方法所屬的類
tag:表示自身常量池的索引
name_index:常量池中的一個有效索引,必須是Utf8類型(表示方法或字段的名字)
descriptor_index:常量池中的一個有效索引,必須是Utf8類型(表示方法的返回類型和參數)
例如
#16 = NameAndType #8:#9 // "<init>":()V
tag = 16
name_index = 8
descriptor_index = 9
查看索引8和9
#8 = Utf8 <init>
#9 = Utf8 ()V
方法名爲<init>表示構造函數,參數0個,返回值爲void
UTF-8結構
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
表示一個字符串常量
tag:表示自身在常量池中的索引
length:表示byte數組的長度
bytes[length]:表示具體數據內容
這個部分其實還有很多細節,不過這裏就不展開了,有興趣的可以自行查看jvm文檔,後面會有文章詳細分析
常量池的內容就介紹到這裏,接下去我們還需要看下類結構的其他成員
this_class,必須是一個有效的常量池索引,需要是CONSTANT_Class_info類型的
super_class,必須是一個有效的常量池索引,需要是CONSTANT_Class_info類型的或者爲0,表示沒有父類
interfaces_count,接口數量,一個int值
interfaces[],接口數組,數組中的值必須是一個常量池的有效索引,需要是CONSTANT_Class_info類型
fields_count,字段數量
fields[],字段數組,數組中的值都是field_info結構
field_info {
u2 access_flags;//access_flag
u2 name_index;//常量池中的一個有效索引,必須是Utf8類型(表示方法或字段的名字)
u2 descriptor_index;//常量池中的一個有效索引,必須是Utf8類型(表示字段的描述)
u2 attributes_count;//跳過,本文不涉及
attribute_info attributes[attributes_count];//跳過,本文不涉及
}
methods_count,方法數量
methods[],方法數組,結構如下
method_info {
u2 access_flags;//access_flag
u2 name_index;//常量池中的一個有效索引,必須是Utf8類型(表示方法或字段的名字)
u2 descriptor_index;//常量池中的一個有效索引,必須是Utf8類型(表示方法的描述)
u2 attributes_count;//屬性數量
attribute_info attributes[attributes_count];//屬性的具體內容
}
class文件的一些基本結構就介紹到這裏,接下去我們進一步深入瞭解class的各種結構究竟是怎麼被構造的
回到generateClassFile()方法的開頭
第一部分,Object方法的預處理
this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);
首先無論是什麼類,都是繼承自Object的,因此Object中的方法是一定需要的
注意,這裏addProxyMethod並非直接寫字節碼了,而是做了一些預處理
我們先看下3個方法中的第一個參數是個啥
在靜態構造函數中,可以看到的確就是Object的3個方法
static {
try {
hashCodeMethod = Object.class.getMethod("hashCode");
equalsMethod = Object.class.getMethod("equals", Object.class);
toStringMethod = Object.class.getMethod("toString");
} catch (NoSuchMethodException var1) {
throw new NoSuchMethodError(var1.getMessage());
}
}
我們進入addProxyMethod方法,這裏對變量名做了一個可讀性處理
String methodName = method.getName();
Class[] paramTypes = method.getParameterTypes();
Class returnType = method.getReturnType();
Class[] exceptionTypes = method.getExceptionTypes();
String cacheKey = methodName + getParameterDescriptors(paramTypes);
Object cache = (List)this.proxyMethods.get(cacheKey);
...
((List) cache).add(new ProxyGenerator.ProxyMethod(methodName, paramTypes, returnType, exceptionTypes, targetClass));
概括而言,就是根據方法的各個要素生成一個ProxyMethod對象,然後將其加入一個緩存List中
接着我們進入ProxyMethod的構造函數查看
private ProxyMethod(String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, Class<?> var6) {
this.methodName = var2;
this.parameterTypes = var3;
this.returnType = var4;
this.exceptionTypes = var5;
this.fromClass = var6;
this.methodFieldName = "m" + ProxyGenerator.this.proxyMethodCount++;
}
值得注意的是,在ProxyMethod的構造函數中有2個字段,在後面會有用到
一個是methodName,表示方法名
另外一個是以m+遞增數字的methodFieldName,表示該方法在最終生成的類中的Method類型的字段的名稱
第二部分,接口方法的預處理
Class[] interfaces = this.interfaces;
int interfaceLength = interfaces.length;
int i;
Class clazz;
for(i = 0; i < interfaceLength; ++i) {
clazz = interfaces[i];
Method[] methods = clazz.getMethods();
int methodLength = methods.length;
for(int j = 0; j < methodLength; ++j) {
Method m = methods[j];
this.addProxyMethod(m, clazz);
}
}
既然生成的類實現了傳入的接口,因此循環接口,將接口的方法要素添加到proxyMethods中,和之前處理Object的方法一樣
第三部分,字段和方法的字節碼寫入
Iterator iterator;
try {
this.methods.add(this.generateConstructor());
iterator = this.proxyMethods.values().iterator();
while(iterator.hasNext()) {
list = (List) iterator.next();
listIterator = list.iterator();
while(listIterator.hasNext()) {
ProxyGenerator.ProxyMethod proxyMethod = (ProxyGenerator.ProxyMethod) listIterator.next();
this.fields.add(new ProxyGenerator.FieldInfo(proxyMethod.methodFieldName, "Ljava/lang/reflect/Method;", 10));
this.methods.add(proxyMethod.generateMethod());
}
}
this.methods.add(this.generateStaticInitializer());
} catch (IOException var10) {
throw new InternalError("unexpected I/O Exception", var10);
}
這裏的第一行,正是寫入構造器的字節碼,這一部分因爲涉及到jvm的執行指令,我們放到之後再詳細看,所以這裏先跳過
this.methods.add(this.generateConstructor());
直接看後面的while循環,就是遍歷之前我們添加的Object和接口定義的方法,然後生成相應的字段字節碼和方法字節碼
while(listIterator.hasNext()) {
ProxyGenerator.ProxyMethod proxyMethod = (ProxyGenerator.ProxyMethod) listIterator.next();
this.fields.add(new ProxyGenerator.FieldInfo(proxyMethod.methodFieldName, "Ljava/lang/reflect/Method;", 10));
this.methods.add(proxyMethod.generateMethod());
}
下面先詳細看看字段字節碼的細節
第四部分,字段字節碼
this.fields.add(new ProxyGenerator.FieldInfo(proxyMethod.methodFieldName, "Ljava/lang/reflect/Method;", 10));
FieldInfo構造函數中
第一個參數proxyMethod.methodFieldName是我們在之前提到的m+遞增數字生成的methodFieldName
第二個參數是類型描述
第三個參數是accessFlag,10表示private static (Modifier.PRIVATE | Modifier.STATIC)
進入構造函數看一下
public FieldInfo(String var2, String var3, int var4) {
this.name = var2;
this.descriptor = var3;
this.accessFlags = var4;
ProxyGenerator.this.cp.getUtf8(var2);
ProxyGenerator.this.cp.getUtf8(var3);
}
回想前文中的field_info類型(忽略attributes)
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
}
this.name、this.descriptor、this.accessFlags正好和field_info中的結構一一對應
同時,由於name_index和descriptor_index都是常量池中的一個索引,因此需要將其寫入常量池
這裏的cp就是指Constant pool,把methodFieldName和descriptor寫入到靜態池
ProxyGenerator.this.cp.getUtf8(var2);
ProxyGenerator.this.cp.getUtf8(var3);
之後我們可以直接看,FieldInfo中的write方法,這就是最後寫入的字節的方法
public void write(DataOutputStream var1) throws IOException {
var1.writeShort(this.accessFlags);
var1.writeShort(ProxyGenerator.this.cp.getUtf8(this.name));
var1.writeShort(ProxyGenerator.this.cp.getUtf8(this.descriptor));
var1.writeShort(0);
}
對照之前的field_info
第一個寫入access_flags
接着寫入name_index和descriptor_index,值都是索引
最後因爲attribute數量是0,因此直接寫0
此時一個完整的字段結構就寫入完畢了
接着我們回頭查看ProxyGenerator.this.cp.getUtf8方法,看看索引是如何確定的
public short getUtf8(String var1) {
if (var1 == null) {
throw new NullPointerException();
} else {
return this.getValue(var1);
}
}
接續查看getValue方法
private short getValue(Object var1) {
Short var2 = (Short)this.map.get(var1);
if (var2 != null) {
return var2;
} else if (this.readOnly) {
throw new InternalError("late constant pool addition: " + var1);
} else {
short var3 = this.addEntry(new ProxyGenerator.ConstantPool.ValueEntry(var1));
this.map.put(var1, new Short(var3));
return var3;
}
}
這裏用map做了一個緩存,key就是需要寫入的字段,value就是索引值,如果命中了map,則直接返回value
如果沒有命中緩存,則需要addEntry
查看addEntry方法
private short addEntry(ProxyGenerator.ConstantPool.Entry var1) {
this.pool.add(var1);
if (this.pool.size() >= 65535) {
throw new IllegalArgumentException("constant pool size limit exceeded");
} else {
return (short)this.pool.size();
}
}
即將生成的entry添加入pool,並返回當前pool的大小,也就是該常量在池中的索引
回想一下cp的結構,其中cp數量是count+1,cp數組有效索引是從1開始的,因此這裏直接返回pool的size,而不是size-1
因此
ProxyGenerator.this.cp.getUtf8()方法做了2件事情
1.將值寫入常量池
2.返回該值在常量池中的索引
到這裏,字段的相關內容就結束了,接下去我們查看方法的字節碼
第五部分,方法字節碼
先看之前while循環中的代碼
this.methods.add(proxyMethod.generateMethod());
查看generateMethod方法
因爲方法的結構體其實包含兩個大部分,第一部分是和field_info一樣的基礎屬性,第二部分是方法的執行體,之後會單獨介紹方法的執行體是怎麼寫入的,這裏我們先關注方法的基本結構
String var1 = ProxyGenerator.getMethodDescriptor(this.parameterTypes, this.returnType);
ProxyGenerator.MethodInfo var2 = ProxyGenerator.this.new MethodInfo(this.methodName, var1, 17);
這裏第一行是獲取方法的描述,類似於 ()V 描述方法的參數和返回參數,這裏()V表示獲取0個參數,返回爲void的方法
第二行就生成一個MethodInfo對象,查看其構造函數
public MethodInfo(String var2, String var3, int var4) {
this.name = var2;
this.descriptor = var3;
this.accessFlags = var4;
ProxyGenerator.this.cp.getUtf8(var2);
ProxyGenerator.this.cp.getUtf8(var3);
ProxyGenerator.this.cp.getUtf8("Code");
ProxyGenerator.this.cp.getUtf8("Exceptions");
}
同樣回顧前文的method_info
method_info {
u2 access_flags;//access_flag
u2 name_index;//常量池中的一個有效索引,必須是Utf8類型(表示方法或字段的名字)
u2 descriptor_index;//常量池中的一個有效索引,必須是Utf8類型(表示方法的描述)
u2 attributes_count;//屬性數量
attribute_info attributes[attributes_count];//屬性的具體內容
}
和field_info不同,除了基礎的access_flags、name_index、descriptor_index外,MethodInfo的構造函數還寫入了2個額外的常量池對象:Code和Exceptions,表示2種attributes
Code表示執行代碼
Exceptions表示方法會拋出的異常
同樣,我們接着就查看MethodInfo中的write方法
寫入access_flags、name_index、descriptor_index
var1.writeShort(this.accessFlags);
var1.writeShort(ProxyGenerator.this.cp.getUtf8(this.name));
var1.writeShort(ProxyGenerator.this.cp.getUtf8(this.descriptor));
寫入屬性的數量
var1.writeShort(2);
此時我們就需要看下attributes的基礎結構了
attribute_info {
u2 attribute_name_index;//名字在常量池的索引
u4 attribute_length;//attribute的字節長度
u1 info[attribute_length];//attribute的實際數據
}
這裏我們就先了解2種具體的attribute,一個是Code,一個是Exception,正是之前在構造函數中看到的
Code的結構
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
此時我們對應着代碼來看
首先寫入attribute_name_index
var1.writeShort(ProxyGenerator.this.cp.getUtf8("Code"));
寫入數據長度attribute_length,這裏的12和8會在本文後面解釋
var1.writeInt(12 + this.code.size() + 8 * this.exceptionTable.size());
寫入棧深max_stack和max_locals本地變量數量,這2個值在下一篇文章的generateMethod()方法詳細介紹中涉及到,這裏就先不展開了
var1.writeShort(this.maxStack);
var1.writeShort(this.maxLocals);
寫入方法執行體字節的長度code_length和方法執行體具體字節code[code_length],這2部分也會在generateMethod()方法詳細介紹中涉及到,這裏就先不展開了
var1.writeInt(this.code.size());
this.code.writeTo(var1);
此時我們看到寫入max_stack、max_locals、code_length時,字段的類型分別是short、short、integer,加起共8個字節
寫入方法會拋出的異常數量exception_table_length
var1.writeShort(this.exceptionTable.size());
這個時候exception_table_length是一個short類型,加上之前的8個字節,一共是10個字節
寫入異常的具體結構
Iterator var2 = this.exceptionTable.iterator();
while(var2.hasNext()) {
ProxyGenerator.ExceptionTableEntry var3 = (ProxyGenerator.ExceptionTableEntry)var2.next();
var1.writeShort(var3.startPc);
var1.writeShort(var3.endPc);
var1.writeShort(var3.handlerPc);
var1.writeShort(var3.catchType);
}
每一個異常都有4個字段,start_pc、end_pc、handler_pc、catch_type,都是short類型,因此一個Exception就會有8個字節,這個8正對應了上面attribute_length中的8
最後寫入attributes自身的attributes_count,因爲沒有,所以直接寫0
var1.writeShort(0);
這個數量是一個short類型,加上之前累積的10個字節,一共12個字節,對應了attribute_length中的12
接下去看Exception
Exception結構
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}
這個結構相對就簡單了很多,下面對應代碼來看
先寫入常量池的索引attribute_name_index
var1.writeShort(ProxyGenerator.this.cp.getUtf8("Exceptions"));
寫入attribute長度attribute_length,這裏的2個2也在後面解釋,不過我想大家自己也能想到分別代表什麼了吧
var1.writeInt(2 + 2 * this.declaredExceptions.length);
寫入異常數量number_of_exceptions,類型是short,對應了第一個2
var1.writeShort(this.declaredExceptions.length);
寫入具體的異常在常量池中的索引,每一個數據都是一個short,對應了第二個2
var1.writeShort(this.declaredExceptions.length);
short[] var6 = this.declaredExceptions;
int var7 = var6.length;
for(int var4 = 0; var4 < var7; ++var4) {
short var5 = var6[var4];
var1.writeShort(var5);
}
以上,字段和方法的寫入就基本解析就完成了
之後將探究generateMethod()方法最複雜的執行體內容
因爲方法的字節碼涉及到了jvm的操作指令,因此我們先做一個基礎性的瞭解
原文地址:https://dzone.com/articles/introduction-to-java-bytecode
jvm指令文檔:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
文中開始介紹的堆、棧、方法區等概念這裏就不詳細描述了,主要看它後面對一些簡單方法的字節碼的解析
首先我們定義一個簡單的類
public class Test {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = a + b;
}
}
編譯生成Test.class
javac Test.java
查看字節碼結構
javap -v Test.class
我們關注其中的main方法部分
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 4
line 6: 8
其中的Code正是方法的執行體,下面按照順序圖解具體操作
iconst_1:將常量1壓入操作棧
istore_1:彈出棧頂的操作數,存入棧的本地變量數組的索引1,也就是變量a
iconst_2:將常量2壓入操作棧
istore_2:彈出棧頂的操作數,存入棧的本地變量數組的索引2,也就是變量b
iload_1:從本地變量索引1種讀取值,並壓入操作棧
iload_2:從本地變量索引2種讀取值,並壓入操作棧
iadd:彈出棧頂的2個操作數,相加後將結果壓入操作棧
istore_3:彈出棧頂的操作數,存入棧的本地變量數組的索引3,也就是變量c
return:從方法返回
如果我們在類中定義一個方法
public class Test {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = calc(a, b);
}
static int calc(int a, int b) {
return (int) Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}
}
得到的字節碼如下,這次我把部分Constant pool也展示在下面
Constant pool:
#1 = Methodref #8.#19 // java/lang/Object."<init>":()V
#2 = Methodref #7.#20 // Test.calc:(II)I
#3 = Double 2.0d
#5 = Methodref #21.#22 // java/lang/Math.pow:(DD)D
#6 = Methodref #21.#23 // java/lang/Math.sqrt:(D)D
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: invokestatic #2 // Method calc:(II)I
9: istore_3
10: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 4
line 6: 10
static int calc(int, int);
descriptor: (II)I
flags: ACC_STATIC
Code:
stack=6, locals=2, args_size=2
0: iload_0
1: i2d
2: ldc2_w #3 // double 2.0d
5: invokestatic #5 // Method java/lang/Math.pow:(DD)D
8: iload_1
9: i2d
10: ldc2_w #3 // double 2.0d
13: invokestatic #5 // Method java/lang/Math.pow:(DD)D
16: dadd
17: invokestatic #6 // Method java/lang/Math.sqrt:(D)D
20: d2i
21: ireturn
LineNumberTable:
line 8: 0
這裏我們主要看一下一些新出現的操作指令
在main方法中,編號6
invokestatic #2:調用靜態方法,方法在Constant Pool中索引爲2,表示Test.calc方法(這裏特別注意,調用的方法目標必須是常量池中的一個有效索引)
在cacl方法中
i2d:將int類型的轉換成double類型的
ldc2_w:將long型或者double型(思考一下爲何是這2種類型放在同一個操作指令中)從靜態池中壓入棧
dadd:將double相加
d2i:將double類型轉換成int類型
ireturn:返回一個int
將上面的jvm指令結合java代碼,就可以初步理解每一行java代碼究竟是如何被jvm執行的了
接下去我們可以通過Proxy的代碼結合實際來看看
方法還是generateClassFile()
在之前“第三部分字節與方法字節碼的寫入”中,有提到
這裏的第一行,正是寫入構造器的字節碼,這一部分因爲涉及到jvm的執行指令,我們放到下篇文章再詳細看,所以這裏先跳過
this.methods.add(this.generateConstructor());
此時我們就可以詳細看下generateConstructor方法究竟幹了什麼
private ProxyGenerator.MethodInfo generateConstructor() throws IOException {
ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
DataOutputStream var2 = new DataOutputStream(var1.code);
this.code_aload(0, var2);
this.code_aload(1, var2);
var2.writeByte(183);
var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
var2.writeByte(177);
var1.maxStack = 10;
var1.maxLocals = 2;
var1.declaredExceptions = new short[0];
return var1;
}
特別注意的是,這裏的var2表示的是方法的執行體部分,也就是在上一篇文章中,我們提到的方法attributes中的一個:Code
接下一行一行分析
初始化MethodInfo對象,3個參數分別是,方法名、方法描述、access_flag,1表示public(參見Modifier.java)
因爲是構造函數,所以方法名爲<init>
方法的描述表示,該方法獲取一個java.lang.reflect.InvocationHandler類型的參數,返回值爲V(表示void)
方法的access_flag爲1,表示public
ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>",
"(Ljava/lang/reflect/InvocationHandler;)V", 1);
在Code中寫入aload_0和aload_1操作指令
this.code_aload(0, var2);
this.code_aload(1, var2);
在Code中寫入183號操作指令,查文檔得:invokespecial
調用實例方法,特別用來處理父類的構造函數
var2.writeByte(183);
在Code中寫入需要調用的方法名和方法的參數
注意,這裏的方法是通過this.cp.getMethodRef方法得到的,也就是說,這裏寫入的最終數據,其實是一個符合該方法描述的常量池中的一個有效索引(這部分知識可以參看之前的3篇文章)
var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>",
"(Ljava/lang/reflect/InvocationHandler;)V"));
在Code中寫入177號指令,查文檔得:return
返回void
var2.writeByte(177);
和上一篇文章中提到的一樣,最後還需要寫入棧深和本地變量數量,以及方法會拋出的異常數量,因爲構造函數不主動拋出異常,所以異常數量直接爲0
注意這裏並非是直接writeByte,而是對MethodInfo的屬性做了一個設置,這部分的字節碼依然會在MethodInfo的write方法中寫入,參見上一篇文章
var1.maxStack = 10;
var1.maxLocals = 2;
var1.declaredExceptions = new short[0];
到此,一個構造方法的結構就完成了
此時我們總結一下該構造函數的結構,當我們查看class文件的結構時,應當是下面這種結構
aload_0;
aload_1;
invokespecial #x //這裏x對應Constant pool中構造函數的編號
return;
驗證一下,我們建立一個類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test extends Proxy {
protected TestClass(InvocationHandler h) {
super(h);
}
}
查看其字節碼
protected Test(java.lang.reflect.InvocationHandler);
descriptor: (Ljava/lang/reflect/InvocationHandler;)V
flags: ACC_PROTECTED
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #1 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return
LineNumberTable:
line 6: 0
line 7: 5
正和我們之前總結的一模一樣
結合之前的一些jvm指令的基本描述,我們就可以對method_info的整體結構有了更深入的瞭解
此時我們先停一停,思考這樣一個問題:
如果由我們自己通過代碼來定義一個Proxy的動態類,我們該如何去定義?
首先回顧一下第一篇文章中提到代理類的3個特性
1.繼承了Proxy類
2.實現了我們傳入的接口
3.以$Proxy+隨機數字的命名
假定我們現在定義一個簡單的接口,並生成該接口的代理類
接口定義
public interface TestInterface {
int put(String a);
}
滿足3個特性的代理類初步定義如下
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class $Proxy11 extends Proxy implements TestInterface {
protected $Proxy11(InvocationHandler h) {
super(h);
}
@Override
public int put(String a) {
return 0;
}
}
然而在這種情況下h的代理是無法生效的,因爲put方法中並沒有h的參與
現在我們回顧一下InvocationHandler的invoke方法的定義
public Object invoke(Object proxy, Method method, Object[] args)
第一個proxy是代理自身,method是被代理的方法,args是方法的參數
因此爲了使得代理生效,我們可以修改方法,如下
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class $Proxy11 extends Proxy implements TestInterface {
protected $Proxy11(InvocationHandler h) {
super(h);
}
@Override
public int put(String a) {
try {
return (int) h.invoke(this, TestInterface.class.getMethod("put", String.class), new Object[]{a});
} catch (Throwable e) {
return 0;
}
}
}
這樣我們就能使得h的代理生效了
當然,這只是我們所設想的最基本的一種代理形式。有了這個思路之後,我們就可以看看源碼中是如何生成方法的字節碼
接着我們來看重點,proxy方法的寫入
還是回到generateClassFile()方法中關注下面這行代碼
this.methods.add(var16.generateMethod());
這個方法就是proxy方法實際執行的code部分了,因爲代碼比較多,所以我就直接將註釋寫到代碼中
如果你前面的內容都仔細閱讀且理解了,那我想你一定會有興趣看完下面所有的代碼,並且會對proxy的實現和class字節碼有更深刻的理解
當然,如果你看到源碼就非常頭疼也沒有關係,可以跳過這部分源碼直接看最後的驗證部分
private ProxyGenerator.MethodInfo generateMethod() throws IOException {
/**
* 獲取方法描述,如果還打開着之前javap的工具的話,就能看到類似於
* // java/lang/Object."<init>":()V
* // Test.calc:(II)I
*/
String methodDescriptor = ProxyGenerator.getMethodDescriptor(this.parameterTypes, this.returnType);
/**
* 這裏和之前構造器一樣,先生成一個MethodInfo對象
* 這裏17表示public final
* Modifier.FINAL | Modifier.PUBLIC
*/
ProxyGenerator.MethodInfo methodInfo = ProxyGenerator.this.new MethodInfo(this.methodName, methodDescriptor, 17);
/**
* 新建一個存放靜態池編號的數組
*/
int[] parameterTypesOrders = new int[this.parameterTypes.length];
/**
* 這個值是指靜態池中的編號,如果還打開着之前javap的話,類似於
* Constant pool:
* #1 = Methodref #8.#19 // java/lang/Object."<init>":()V
* #2 = Methodref #7.#20 // Test.calc:(II)I
* #3 = Double 2.0d
* #5 = Methodref #21.#22 // java/lang/Math.pow:(DD)D
* 前面的#1,#2,#3,#5
* 我們注意到缺少了#4,因爲double需要佔用8個字節,而其他的都只需要佔用4個字節
*/
int constantPoolNumber = 1;
for(int i = 0; i < parameterTypesOrders.length; ++i) {
parameterTypesOrders[i] = constantPoolNumber;
/**
* 如果是Long或者Double類型的參數,則+2,否則+1,因爲Long和Double都是佔用8個字節
*/
constantPoolNumber += ProxyGenerator.getWordsPerType(this.parameterTypes[i]);
}
DataOutputStream dataOutputStream = new DataOutputStream(methodInfo.code);
/**
* aload_0,加載棧幀本地變量表的第一個參數,因爲是實例方法,所以是就是指this
*/
ProxyGenerator.this.code_aload(0, dataOutputStream);
/**
* getfield,獲取this的實例字段
*/
dataOutputStream.writeByte(180);
/**
* 從Proxy類中,獲取類型是InvocationHandler,字段名爲h的對象
*/
dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef("java/lang/reflect/Proxy", "h", "Ljava/lang/reflect/InvocationHandler;"));
/**
* aload_0
*/
ProxyGenerator.this.code_aload(0, dataOutputStream);
/**
* getstatic,獲取靜態字段
*/
dataOutputStream.writeByte(178);
/**
* 獲取當前代理類,名字是methodFieldName,類型是Method的對象(之前在寫入靜態池的時候,用的也是methodFieldName)
*/
dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef(ProxyGenerator.dotToSlash(ProxyGenerator.this.className), this.methodFieldName, "Ljava/lang/reflect/Method;"));
/**
* 準備寫入參數
*/
if (this.parameterTypes.length > 0) {
/**
* 寫入參數的數量,如果再仔細看一下code_ipush
* 當length小於等於5時,寫入的命令是iconst_m1~iconst_5
* 當length在-128~127閉區間時,寫入的命令是bipush
* 否則就寫入sipush
*/
ProxyGenerator.this.code_ipush(this.parameterTypes.length, dataOutputStream);
/**
* anewarray,創建一個數組
*/
dataOutputStream.writeByte(189);
/**
* 數組的類型是object
*/
dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/Object"));
/**
* 循環參數
*/
for(int i = 0; i < this.parameterTypes.length; ++i) {
/**
* dup,複製棧頂的操作數
*/
dataOutputStream.writeByte(89);
/**
* iconst、bipush、sipush
*/
ProxyGenerator.this.code_ipush(i, dataOutputStream);
/**
* 對參數類型等做一個編碼
*/
this.codeWrapArgument(this.parameterTypes[i], parameterTypesOrders[i], dataOutputStream);
/**
* aastore,將對象存入數組
*/
dataOutputStream.writeByte(83);
}
} else {
/**
* 如果沒參數的話
* aconst_null,push一個null
*/
dataOutputStream.writeByte(1);
}
/**
* invokeinterface 調用接口方法
*/
dataOutputStream.writeByte(185);
/**
* 找到InvocationHandler的invoke方法
*/
dataOutputStream.writeShort(ProxyGenerator.this.cp.getInterfaceMethodRef("java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"));
/**
* iconst_1,將1壓入操作棧
*/
dataOutputStream.writeByte(4);
/**
* nop,不做事情
*/
dataOutputStream.writeByte(0);
if (this.returnType == Void.TYPE) {
/**
* 如果是void方法
* pop,將棧頂的操作數彈出
*/
dataOutputStream.writeByte(87);
/**
* return
*/
dataOutputStream.writeByte(177);
} else {
/**
* 對返回值進行編碼
*/
this.codeUnwrapReturnValue(this.returnType, dataOutputStream);
}
byte startPc = 0;
short handlerPc;
short endPc = handlerPc = (short)methodInfo.code.size();
/**
* 獲取方法可能拋出的異常
*/
List catchList = ProxyGenerator.computeUniqueCatchList(this.exceptionTypes);
if (catchList.size() > 0) {
Iterator exceptionIterator = catchList.iterator();
/**
* 對異常進行預處理
*/
while(exceptionIterator.hasNext()) {
Class var12 = (Class)exceptionIterator.next();
/**
* 這裏注意startPc, endPc, handlerPc參數,和pc register有關,用於拋出Exception時能確定接下去要執行的指令
*/
methodInfo.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(var12.getName()))));
}
/**
* athrow,拋出異常
*/
dataOutputStream.writeByte(191);
/**
* 重新獲取異常的處理點
*/
handlerPc = (short)methodInfo.code.size();
/**
* 添加異常的基類
*/
dataOutputStream.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass("java/lang/Throwable")));
/**
* 根據constantPoolNumber的值
* astore_0 = 75 (0x4b)
* astore_1 = 76 (0x4c)
* astore_2 = 77 (0x4d)
* astore_3 = 78 (0x4e)
* astore
*/
ProxyGenerator.this.code_astore(constantPoolNumber, dataOutputStream);
/**
* new 創建一個新對象
*/
dataOutputStream.writeByte(187);
/**
* 對象是UndeclaredThrowableException
*/
dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/reflect/UndeclaredThrowableException"));
/**
* dup 複製棧頂操作數
*/
dataOutputStream.writeByte(89);
/**
* 根據constantPoolNumber的值
* aload_0 = 42 (0x2a)
* aload_1 = 43 (0x2b)
* aload_2 = 44 (0x2c)
* aload_3 = 45 (0x2d)
* aload
*/
ProxyGenerator.this.code_aload(constantPoolNumber, dataOutputStream);
/**
* invokespecial,調用父類的方法
*/
dataOutputStream.writeByte(183);
/**
* 父類的構造函數
*/
dataOutputStream.writeShort(ProxyGenerator.this.cp.getMethodRef("java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V"));
/**
* athrow,拋出異常
*/
dataOutputStream.writeByte(191);
}
if (var2.code.size() > 65535) {
throw new IllegalArgumentException("code size limit exceeded");
} else {
var2.maxStack = 10;
var2.maxLocals = (short)(var4 + 1);
var2.declaredExceptions = new short[this.exceptionTypes.length];
for(int var14 = 0; var14 < this.exceptionTypes.length; ++var14) {
var2.declaredExceptions[var14] = ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(this.exceptionTypes[var14].getName()));
}
return var2;
}
}
那麼爲了看看我們一開始對於方法的猜測是否正確,我們略微改造之前定義的接口和類,然後實際看看
接口和Proxy定義(因爲字節碼中還包含了一些異常的信息,所以定義接口的時候特別定義了2個異常)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.concurrent.TimeoutException;
public class Proxy11 extends Proxy implements TestInterface {
protected Proxy11(InvocationHandler h) {
super(h);
}
public void put(String a, Double b) throws TimeoutException {
try {
h.invoke(this, TestInterface.class.getMethod("put", String.class, Double.class), new Object[]{a, b});
} catch (Throwable e) {
}
}
public int get(String a, Long b) throws IndexOutOfBoundsException {
try {
return (int) h.invoke(this, TestInterface.class.getMethod("get", String.class, Long.class), new Object[]{a, b});
} catch (Throwable e) {
return 0;
}
}
}
interface TestInterface {
void put(String a, Double b) throws TimeoutException;
int get(String a, Long b) throws IndexOutOfBoundsException;
}
我們生成class後,將字節碼的指令集與我們之前的分析一一對比,雖然其中還是有些不同,不過大體上是符合之前源碼的順序
最後爲了實際考察Proxy生成類的源碼,我們還是需要將Proxy的字節碼轉換回java文件
首先我們需要添加vm啓動參數
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
有了這個參數,當我們使用Proxy時,就會把class寫入到文件中了
寫入的目錄是項目下的com/sun/proxy/$Proxy11.class
爲了更好地可讀性,我們需要使用一個在線工具
http://www.javadecompilers.com/
傳入我們之前生成出來class文件
結果如下
package com.sun.proxy;
import java.util.concurrent.TimeoutException;
import java.lang.reflect.UndeclaredThrowableException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import cn.tera.aopproxy.TestInterface;
import java.lang.reflect.Proxy;
public final class $Proxy11 extends Proxy implements TestInterface
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy11(final InvocationHandler h) {
super(h);
}
public final boolean equals(final Object o) {
try {
return (boolean)super.h.invoke(this, $Proxy11.m1, new Object[] { o });
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
public final int get(final String s, final Long n) throws IndexOutOfBoundsException {
try {
return (int)super.h.invoke(this, $Proxy11.m3, new Object[] { s, n });
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, $Proxy11.m2, null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
public final void put(final String s, final Double n) throws TimeoutException {
try {
super.h.invoke(this, $Proxy11.m4, new Object[] { s, n });
}
catch (Error | RuntimeException | TimeoutException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
public final int hashCode() {
try {
return (int)super.h.invoke(this, $Proxy11.m0, null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
static {
try {
$Proxy11.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
$Proxy11.m3 = Class.forName("cn.tera.aopproxy.TestInterface").getMethod("get", Class.forName("java.lang.String"), Class.forName("java.lang.Long"));
$Proxy11.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
$Proxy11.m4 = Class.forName("cn.tera.aopproxy.TestInterface").getMethod("put", Class.forName("java.lang.String"), Class.forName("java.lang.Double"));
$Proxy11.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
}
catch (NoSuchMethodException ex) {
throw new NoSuchMethodError(ex.getMessage());
}
catch (ClassNotFoundException ex2) {
throw new NoClassDefFoundError(ex2.getMessage());
}
}
}
是不是有一種恍然大悟的感覺,此時再回頭去看之前分析的方法字節碼,就能更好地理解其含義了,以及和我們自己定義的類的字節碼有區別的原因了。
當然我們更可以直接查看生成的class文件,再通過javap去查看字節碼,然後返過去和前面的源碼再作對比,這個就留給讀者自己去分析了
至此,java動態代理的根本原理和相應的class字節碼結構的分析就結束了