动态传入表名 注解_Java动态代理的原理及源码分析

本文深入探讨了Java动态代理的原理,通过分析`Proxy.newProxyInstance`方法,揭示了代理类的生成过程,包括类名的确定、字节码的生成等。文章还介绍了动态代理类的构造、字段和方法的字节码写入,并重点讨论了字节码生成的关键部分。
摘要由CSDN通过智能技术生成

在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的态度,于是对java动态代理的本质原理做了一些研究,于是便有了这篇文章

为了尽快进入正题,这里先跳过spring aop和java动态代理的使用流程的讲解

不过,我们首先还是先看下java dynamic proxy的基本使用方法,假定我们要代理的对象是一个Map,则代码如下:

Map proxyInstance =(Map)Proxy.newProxyInstance( HashMap.class.getClassLoader(), newClass[]{Map.class}, newDynamicInvocationHandler());

之后proxyInstance就可以作为一个正常的Map对象进行使用了

为了对生成对象的属性做一个基本的了解,我们先打印一下proxyInstance的实际类型名称

System.out.println(proxyInstance.getClass().getName());

得到结果

com.sun.proxy.$Proxy11

如果使用多了,就会发现所有的代理类的名称都是$Proxy加一个数字,且包名是com.sun.proxy

当我们查看Proxy.newProxyInstance方法时,会发现它返回的其实是一个Object对象

publicstaticObjectnewProxyInstance(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);

这个就是实际生成类的方法,后面我们会继续深究,先略放一放

第二部分,实例的创建

finalConstructor> cons = cl.getConstructor(constructorParams);finalInvocationHandler ih = h;...return cons.newInstance(newObject[]{h});

最终对象的实例化过程就是通过之前生成的class,获取其指定参数的构造函数,并将InvocationHandler对象传入

查看constructorParams字段

/** parameter types of a proxy class constructor */privatestaticfinalClass>[] 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}. */protectedProxy(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. */privatestaticClass>getProxyClass0(ClassLoader loader, Class>... interfaces){ if(interfaces.length >65535){ thrownewIllegalArgumentException("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 */privatestaticfinalWeakCache[],Class>> proxyClassCache =newWeakCache<>(newKeyFactory(),newProxyClassFactory());

该对象本质就是一个类似于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. */privatestaticfinalclassProxyClassFactory implementsBiFunction[],Class>>

这个就是我们要寻找的负责具体生成类的工厂了,查看其apply方法

首先其会对传入的接口类型做一些校验,包括loader能否加载到传入的接口,接口是否实际上是接口(因为数组的类型是Class),接口是否有重复

Map,Boolean> interfaceSet =newIdentityHashMap<>(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){ thrownewIllegalArgumentException( intf +" is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if(!interfaceClass.isInterface()){ thrownewIllegalArgumentException( interfaceClass.getName()+" is not an interface"); } /* * Verify that this interface is not a duplicate. */ if(interfaceSet.put(interfaceClass,Boolean.TRUE)!=null){ thrownewIllegalArgumentException( "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; }elseif(!pkg.equals(proxyPkg)){ thrownewIllegalArgumentException( "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 namesprivatestaticfinalString proxyClassNamePrefix ="$Proxy";

将上面三者组合成最终的类名(回想之前我们打印出的实例的类名)

String proxyName = proxyPkg + proxyClassNamePrefix + num;

上面这几个步骤确定了类的名称,但还是皮毛,接下去是生成类的血肉:字节码

/* * Generate the specified proxy class. */byte[] proxyClassFile =ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);

具体的探究也先放一下,先看字节码转换成具体类的方法

try{ returndefineClass0(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). */ thrownewIllegalArgumentException(e.toString());}

而该方法是一个native的方法,所以暂时就无法继续探究了,不过知道了这个方法后,如果我们自己有需要,也可以利用这种机制实现自己的动态类生成,后面会想办法做一个demo,本文就不做探讨了

privatestaticnativeClass>defineClass0(ClassLoader loader,String name, byte[] b,int off,int len);


之前其实都是开胃菜,现在回到之前生成字节码的方法,查看方法源码

publicstaticbyte[]generateProxyClass(finalString var0,Class>[] var1,int var2){ ProxyGenerator var3 =newProxyGenerator(var0, var1, var2); finalbyte[] var4 = var3.generateClassFile(); if(saveGeneratedFiles){ AccessController.doPrivileged(newPrivilegedAction(){ publicVoidrun(){ 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,newOpenOption[0]); returnnull; }catch(IOException var4x){ thrownewInternalError("I/O exception saving generated file: "+ var4x); } } }); } return var4;}

中间if部分的代码可以先忽略,不过我们会在后面的文章中使用到这部分功能,这里先关注下面这2行代码

ProxyGenerator var3 =newProxyGenerator(var0, var1, var2);finalbyte[] 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 =newByteArrayOutputStream();DataOutputStream data =newDataOutputStream(byteStream);

写入固定开头magic,这里-889275714就是对应0xCAFEBABE

data.writeInt(-889275714);

写入版本号

data.writeShort(0);//minor_versiondata.writeShort(49);//major_version

写入常量池,这里cp就是指constant pool

this.cp.write(data);

这里我们需要进入cp的write方法看一下,也先不要纠结Entry的细节,我们还是先把握整体

publicvoidwrite(OutputStream var1)throwsIOException{ DataOutputStream var2 =newDataOutputStream(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

publicclassTestimplementsTestInt{ privateint field =1; publicintadd(int a,int b){ return a + b; }}interfaceTestInt{}

生成.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."":()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 #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 // "":()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."":()V 4: aload_0 5: iconst_1 6: putfield #2 // Field field:I 9:return LineNumberTable: line 1:0 line 2:4 public intadd(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."":()V #2= Fieldref #3.#17 // Test.field:I #3= Class #18 // Test ... #6= Utf8 field ... #16= NameAndType #8:#9 // "":()V

其中有如下几种类型

Methodref :方法的引用

Fieldref:字段的引用

Class :类的引用

Utf8 :字符串的引用

NameAndType 类型的描述

下面依据jvm文档,一个一个介绍

Class结构

CONSTANT_Class_info { u1 tag; u2 name_index; }

表示一个类的引用

tag

:表示自身在常量池中的索引

name_index

:必须是常量池中的有效索引,用来表示类的名字

例如

3 = Class #18 // Test

01

tag = 3,表示自身索引为3

name_index = 18,表示名字的索引是18

此时我们查看#18,即这个类的名字是Test

18 = Utf8 Test

02

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."":()V

03

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 // "":()V

04

即表示这个方法是Object类中的构造函数

NameAndType结构

CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }

用来表示一个方法或者字段,其中不包括该字段或方法所属的类

tag

:表示自身常量池的索引

name_index

:常量池中的一个有效索引,必须是Utf8类型(表示方法或字段的名字)

descriptor_index

:常量池中的一个有效索引,必须是Utf8类型(表示方法的返回类型和参数)

例如

16= NameAndType #8:#9 // "":()V

05

tag = 16

name_index = 8

descriptor_index = 9

查看索引8和9

8= Utf8 #9= Utf8 ()V

06

方法名为表示构造函数,参数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){ thrownewNoSuchMethodError(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(newProxyGenerator.ProxyMethod(methodName, paramTypes, returnType, exceptionTypes, targetClass));

概括而言,就是根据方法的各个要素生成一个ProxyMethod对象,然后将其加入一个缓存List中

接着我们进入ProxyMethod的构造函数查看

privateProxyMethod(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(newProxyGenerator.FieldInfo(proxyMethod.methodFieldName,"Ljava/lang/reflect/Method;",10)); this.methods.add(proxyMethod.generateMethod()); } } this.methods.add(this.generateStaticInitializer());}catch(IOException var10){ thrownewInternalError("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(newProxyGenerator.FieldInfo(proxyMethod.methodFieldName,"Ljava/lang/reflect/Method;",10)); this.methods.add(proxyMethod.generateMethod());}

下面先详细看看字段字节码的细节

第四部分,字段字节码

this.fields.add(newProxyGenerator.FieldInfo(proxyMethod.methodFieldName,"Ljava/lang/reflect/Method;",10));

FieldInfo构造函数中

第一个参数proxyMethod.methodFieldName是我们在之前提到的m+递增数字生成的methodFieldName

第二个参数是类型描述

第三个参数是accessFlag,10表示private static (Modifier.PRIVATE | Modifier.STATIC)

进入构造函数看一下

publicFieldInfo(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方法,这就是最后写入的字节的方法

publicvoidwrite(DataOutputStream var1)throwsIOException{ 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方法,看看索引是如何确定的

publicshortgetUtf8(String var1){ if(var1 ==null){ thrownewNullPointerException(); }else{ returnthis.getValue(var1); }}

接续查看getValue方法

privateshortgetValue(Object var1){ Short var2 =(Short)this.map.get(var1); if(var2 !=null){ return var2; }elseif(this.readOnly){ thrownewInternalError("late constant pool addition: "+ var1); }else{ short var3 =this.addEntry(newProxyGenerator.ConstantPool.ValueEntry(var1)); this.map.put(var1,newShort(var3)); return var3; }}

这里用map做了一个缓存,key就是需要写入的字段,value就是索引值,如果命中了map,则直接返回value

如果没有命中缓存,则需要addEntry

查看addEntry方法

privateshortaddEntry(ProxyGenerator.ConstantPool.Entry var1){ this.pool.add(var1); if(this.pool.size()>=65535){ thrownewIllegalArgumentException("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.newMethodInfo(this.methodName, var1,17);

这里第一行是获取方法的描述,类似于 ()V 描述方法的参数和返回参数,这里()V表示获取0个参数,返回为void的方法

第二行就生成一个MethodInfo对象,查看其构造函数

publicMethodInfo(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的操作指令,因此我们先做一个基础性的了解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值