整理Classworking工具箱泛型与ASM.docx

上传人:b****5 文档编号:4440723 上传时间:2022-12-01 格式:DOCX 页数:16 大小:29.62KB
下载 相关 举报
整理Classworking工具箱泛型与ASM.docx_第1页
第1页 / 共16页
整理Classworking工具箱泛型与ASM.docx_第2页
第2页 / 共16页
整理Classworking工具箱泛型与ASM.docx_第3页
第3页 / 共16页
整理Classworking工具箱泛型与ASM.docx_第4页
第4页 / 共16页
整理Classworking工具箱泛型与ASM.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

整理Classworking工具箱泛型与ASM.docx

《整理Classworking工具箱泛型与ASM.docx》由会员分享,可在线阅读,更多相关《整理Classworking工具箱泛型与ASM.docx(16页珍藏版)》请在冰豆网上搜索。

整理Classworking工具箱泛型与ASM.docx

整理Classworking工具箱泛型与ASM

Java™5泛型提供了对于许多classworking都非常有用的信息。

尽管Java反射可用于为载入的类获取泛型信息,但要求类必须载入到JVM中,这是一个很大的缺点。

在本文中,classworking精神领袖DennisSosnoski展示了ASMJava字节码操纵框架怎样在无需经过Javaclassloading处理的情况下提供对泛型信息的灵活访问。

在文中,他还深入探讨了泛型的二进制类表示。

Java5程序中的泛型信息对于理解程序的数据结构非常有帮助。

在上一期中,我为您介绍了如何使用运行时反射来访问泛型信息。

如果您仅对获得载入JVM中的类的信息感兴趣,那么这种反射方法非常有效。

但有时您可能希望在载入类之前对其加以修改,或者希望在不载入类的情况下研究数据结构。

在这样的时候,反射对您来说就不再是一种行之有效的办法——反射将JVM类结构作为信息源使用,因此它仅对已由JVM装载的类起作用。

要想在不将类载入JVM的情况下访问泛型信息,您需要一种读取存储在二进制类表示内的泛型信息的方法。

在前几期文章中,已经介绍过ASMclassworking库是怎样提供了一种清洁的接口,以读取及写入二进制类。

在这篇文章中,我将向您展示如何利用ASM从类文件中获取原始泛型信息,如何以一种有用的方式解释泛型。

在钻研ASP细节之前,让我们首先来看看泛型信息编码到二进制类中的实际方式。

跟踪泛型

为将可由Java编译器使用的键入信息添加到Java二进制类中,需要使用泛型规范设计器。

幸运的是,Java平台已有一种内置于二进制类格式中的机制,可用于此目的。

这种机制就是属性结构(attributestructure),它主要使所有类型的信息可与类本身或类的方法、字段及其他组件相关联。

某些类型的属性信息是由JVM规范定义的,但Java语言的原始设计器作出了明智的选择,将一组可能出现的属性保留为开放,从而可由新版本的规范加以扩展,也可由用户扩展以设计其自己的自定义属性。

泛型信息存储在一个新的标准属性中:

签名属性。

该属性是一个简单的文本值,为类、字段、方法或变量解码泛型信息。

更新的Java5JVM规范(参考资料部分给出了Java5更改页面的链接)清楚地说明了签名文本值的完整语法。

在这里我不打算加以详述,但本节稍后的部分中会简单介绍签名。

首先将介绍一些必备的背景信息,以使您了解类名称的内部结构及JVM所使用的字段和方法描述符。

深入内部

Java平台中的类总是来自某些包。

当您在Java源代码中引用类名称时,您或许会也或许不会真正将包限定作为名称的一部分。

您总是可以包含包限定(形如java.lang.String),但您也可以为了省事而忽略它——如果类来自java.lang包或已import到源文件中。

这种包含包限定的类名称结构就称为“完全限定”类名。

在实际的二进制类内部,类名称总是在一个包中指定的。

但这种名称的格式与Java源代码中的完全限定类名略有差别,使用正斜杠(“/”)取代圆点(“.”)。

例如,在String类中,名称的内部形式为java/lang/String。

如果您尝试将一个类文件作为文本输出或查看,那么通常会看到上述形式的多个字符串,每个字符串都是对某个类的引用。

采用这种内部形式的类引用是作为字段和方法描述符的一部分使用的。

字段描述符指定类中定义的一个类的准确类型。

所使用的表示法取决于字段是简单对象类型、简单原语类型还是数组类型。

简单对象类型的表示法为,以‘L’开头,后接对象类名称的内部形式,以‘;’结尾。

原语类型的表示法为,各类型使用一个单独的字母(如‘I’表示int、‘Z’表示布尔型)。

数组类型的表示法为,以‘[’作为数组项类型(其本身也可为数组类型)的前缀修饰符。

表1给出了关于各字段描述符的示例,另外还列出了相应的Java源代码声明:

表1.字段描述符示例

描述符

源代码

Ljava/lang/String;

String

I

int

[Ljava/lang/Object;

Object[]

[Z

boolean[]

[[Lcom/sosnoski/generics/FileInfo;

com.sosnoski.generics.FileInfo[][]

方法描述符结合了字段描述符,以指定方法的参数类型和返回类型。

方法描述符的格式非常易于理解。

以‘(’开始,后接参数的字段描述符(均一起运行),随后是‘)’,最后以返回类型结尾(若返回类型为void,则以‘V’结尾)。

表2给出了方法描述符的一些示例,同时还列出了相应的Java源代码声明(注意方法名称和参数名称本身并非方法描述符的一部分,所以在表中使用了占位符):

表2.方法描述符示例

描述符

源代码

(Ljava/lang/String;)I

intmmm(Stringx)

(ILjava/lang/String;)V

voidmmm(intx,Stringy)

(I)Ljava/lang/String;

Stringmmm(intx)

(Ljava/lang/String;)[C

char[]mmm(Stringx)

(ILjava/lang/String;[[Lcom/sosnoski/generics/FileInfo;)V

voidmmm(intx,Stringy,FileInfo[][]z)

在虚线处签名

上面已经介绍了字段和方法描述符,那么接下来将介绍签名。

签名格式扩展了字段和方法描述符的概念,将泛型类型信息包含于其中。

不幸的是,泛型的复杂性(包括可能出现的各种上下界变化等)意味着签名无法像描述符那样简单地说明。

签名的语法(详见JVMspecificationchangesforJava1.5的第4章)包含21个独立产品项。

本文无法全面涉及,这里将给出几个示例,下一节将针对这部分示例展开讲解。

清单1是上一期文章中所用的一个数据结构类的部分源代码,以及相应的签名字符串。

在本例中,类本身并非参数化类型,但字段和方法使用了参数化的java.util.List:

清单1.简单的签名示例

publicclassDirInfo

{

privatefinalListm_files;

privatefinalListm_directories;

...

publicListgetDirectories(){

returnm_directories;

}

publicListgetFiles(){

returnm_files;

}

...

}

Classsignature:

{none}

m_filessignature:

Ljava/util/List;

m_directoriessignature:

Ljava/util/List;

getDirectories()signature:

()Ljava/util/List;

getFiles()signature:

()Ljava/util/List;

由于类并非参数化类型,所以未为该类本身的二进制类表示添加任何签名。

但确实为使用参数化类型的字段和方法使用了签名。

m_files字段签名表示这是一个List,且类型为FileInfo;而m_directories字段签名则表示这是一个类型为DirInfo的List。

同样,getDirectories()方法签名表示该方法返回一个类型为DirInfo的List,而getFiles()签名则表示该方法返回一个类型为FileInfo的List。

迄今为止,一切看起来都相当容易理解,但事实真是如此吗?

下面让我们看看清单2,其中给出了一个简单的参数化类定义和相应的签名字符串:

清单2.参数化类签名示例

publicclassPairCollectionimplementsIterable

{

/**Collectionwithfirstcomponentvalues.*/

privatefinalArrayListm_tValues;

/**Collectionwithsecondcomponentvalues.*/

privatefinalArrayListm_uValues;

...

publicvoidadd(Tt,Uu){

m_tValues.add(t);

m_uValues.add(u);

}

publicUget(Tt){

intindex=m_tValues.indexOf(t);

if(index>=0){

returnm_uValues.get(index);

}else{

returnnull;

}

}

...

}

Classsignature:

Ljava/lang/Object;U:

Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/Iterable;

m_tValuessignature:

Ljava/util/ArrayList;

m_uValuessignature:

Ljava/util/ArrayList;

addsignature:

(TT;TU;)V

getsignature:

(TT;)TU;

由于清单2中的类为参数化类型,所以类签名需要以二进制类形式表示。

与源代码相比,签名的文本要长一些,但如果您了解到,源代码中省略的类型参数的所有可选组件都包含在签名中,那么理解起来也就不太困难了。

签名的第一部分(位于尖括号‘<...>’内)就是该类的类型参数定义清单。

这些定义的形式都相同,类型参数名称后接类型的类边界和接口边界(若存在)的字段描述符。

各字段描述符前加‘:

’字符。

由于清单2源代码未为类的类型参数指定任何边界,因此其边界均为默认的类边界java.lang.Object。

类签名的第二部分(尖括号外)给出了超类和超接口(若存在)的签名。

在清单2所示的例子中,未指定任何超类,因此签名以java.lang.Object作为超类。

这里指定了超接口,为Iterable

在签名中可以看到预期结果,源代码中使用的只是‘’,而签名中使用的是‘’。

原因在于签名需要区分类名称和类型变量名称,第一个‘T’标识紧随其后的内容为类型变量名,而结尾的‘;’表示名称结束。

清单2中的字段和方法签名利用了与超接口签名相同的变量格式类型,其他都与前面介绍的内容相同。

回页首

ASM中的泛型

本系列的前几期文章中已介绍过(链接参见参考资料部分),ASM使用了一种访问器(visitor)模式来处理二进制类表示。

这种访问器模式是双向的:

您可以解析一个现有类,得到类组件的处理程序访问器方法的调用序列,也可以实现对类写入器的访问器方法的同类调用序列,以生成一个二进制类表示。

这一解析器/写入器对称使ASM在您仅修改类的特定方面的情况下尤为方便——您可将类写入器作为类解析器事件的处理程序的基础,仅重写基写入器来处理您想更改的事件。

解析器(或读取器)和写入器都是非常有用的独立组件。

ASM2.X全面支持Java5JVM更改,包括读取和写入签名。

签名的基本处理是通过直接传递给恰当的访问器方法的值自动实现的。

另外,ASM2.X还增加了对签名字符串(有时非常复杂)编码进行解析的支持,从而可翻译签名细节。

按照ASM的基本原理,相同的接口还可供写入器使用以按需生成签名字符串。

在这一节中,我将介绍ASM如何将基本签名作为textblob处理,又是如何详细解析基本签名的。

所有部分的签名

ASM中将签名作为textblob处理,这一方式直接内建于基本类、字段和方法的访问器调用中。

清单3展示了org.objectweb.asm.ClassVisitor接口中的相应方法:

清单3.类、字段和方法的访问器方法

publicinterfaceClassVisitor

{

voidvisit(intversion,intaccess,Stringname,Stringsignature,

StringsuperName,String[]interfaces);

FieldVisitorvisitField(intaccess,Stringname,Stringdesc,

Stringsignature,Objectvalue);

MethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,

Stringsignature,String[]exceptions);

...

}

清单中的各访问器方法将签名字符串作为参数。

若相应的类、字段或方法非泛型,则在调用方法时将返回null值。

清单4显示了签名相关方法的实际应用。

其中用mons.EmptyVisitor类作为基础实现了一个访问器类,这样我只需重写想使用的方法即可。

所提供的方法实现仅输出整体签名信息、本清单所示类中各字段和方法的描述符和签名信息。

清单4的末尾处展示了在清单1所示的完整DirInfo类中使用此访问器时所生成的输出:

清单4.签名相关方法的实际应用

publicclassShowSignaturesVisitorextendsEmptyVisitor

{

publicvoidvisit(intversion,intaccess,Stringname,Stringsig,

Stringsname,String[]inames){

System.out.println("Class"+name+"signature:

");

System.out.println(""+sig);

super.visit(version,access,name,sig,sname,inames);

}

publicFieldVisitorvisitField(intaccess,Stringname,Stringdesc,

Stringsig,Objectvalue){

System.out.println("Field"+name+"descriptorandsignature:

");

System.out.println(""+desc);

System.out.println(""+sig);

returnsuper.visitField(access,name,desc,sig,value);

}

publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,

Stringsig,String[]exceptions){

System.out.println("Method"+name+"()descriptorandsignature:

");

System.out.println(""+desc);

System.out.println(""+sig);

returnsuper.visitMethod(access,name,desc,sig,exceptions);

}

}

Classcom/sosnoski/generics/DirInfosignature:

null

Fieldm_filesdescriptorandsignature:

Ljava/util/List;

Ljava/util/List;

Fieldm_directoriesdescriptorandsignature:

Ljava/util/List;

Ljava/util/List;

Fieldm_lastModifydescriptorandsignature:

Ljava/util/Date;

null

Method()descriptorandsignature:

(Ljava/io/File;)V

null

MethodgetDirectories()descriptorandsignature:

()Ljava/util/List;

()Ljava/util/List;

MethodgetFiles()descriptorandsignature:

()Ljava/util/List;

()Ljava/util/List;

MethodgetLastModify()descriptorandsignature:

()Ljava/util/Date;

null

签名分析

除将签名作为字符串处理外,ASM还支持在细节级处理签名。

org.objectweb.asm.signature.SignatureReader类解析一个签名字符串,并生成对org.objectweb.asm.signature.SignatureVisitor接口的调用序列。

org.objectweb.asm.signature.SignatureWriter类实现访问器接口,并从访问器方法调用序列中构建出签名字符串。

很不幸,细节级接口有些复杂,但其原因在于签名定义的复杂性,而不是ASM代码处理不力。

SignatureVisitor接口展现了这一复杂性,它定义了16个可在签名处理过程中包含的独立方法调用。

当然,绝大多数签名仅使用这些方法中的一小部分。

为举例说明ASM的细节级签名处理,我将解析本文前面所讨论的某些签名,从而介绍方法。

为此,我编写了TraceSignatureVisitor类,清单5展示了该类的部分代码,该清单中的AnalyzeSignaturesVisitor用于驱动签名处理。

当AnalyzeSignaturesVisitor用做类的访问器时,它会为所发现的各签名创建一个SignatureReader,将TraceSignatureVisitor类的一个实例作为签名组件访问器调用的目标传递。

用于解析签名的SignatureReader调用取决于签名的形式:

对于类和方法签名,恰当的方法是accept();对于字段签名,应使用acceptType()调用。

清单5.签名分析

publicclassTraceSignatureVisitorimplementsSignatureVisitor

{

publicvoidvisitFormalTypeParameter(Stringname){

System.out.println("visitFormalTypeParameter("+name+")");

}

publicSignatureVisitorvisitClassBound(){

System.out.println("visitClassBound()");

returnthis;

}

publicSignatureVisitorvisitInterfaceBound(){

System.out.println("visitInterfaceBound()");

returnthis;

}

publicSignatureVisitorvisitSuperclass(){

System.out.println("visitSuperclass()");

returnthis;

}

publicSignatureVisitorvisitInterface(){

System.out.println("visitInterface()");

returnthis;

}

publicSignatureVisitorvisitParameterType(){

System.out.println("visitParameterType()");

returnthis;

}

...

}

publicclassAnalyzeSignaturesVisitorextendsEmptyVisitor

{

publicvoidvisit(intversion,intaccess,Stringname,Stringsig,

Stringsname,String[]inames){

if(sig!

=null){

System.out.println("Class"+name+"signature:

");

System.out.println(""+sig);

newSignatureReader(sig).accept(newTraceSignatureVisitor());

}

super.visit(version,access,name,sig,sname,inames);

}

publicFieldVisitorvisitField(intaccess,Stringname,Stringdesc,

Stringsig,Objectvalue){

if(sig!

=null){

System.out.println("Field"+name+"signature:

");

System.out.println(""+sig);

newSignatureReader(sig).acceptType(newTraceSignatureVisitor());

}

r

展开阅读全文
相关搜索

当前位置:首页 > 高中教育 > 数学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1