我们都知道在计算机中通过控制器识别机器指令来进行工作,而不同的操作系统其机器指令集不尽相同,为了屏蔽不同操作系统之间机器指令之间的差异,java引入java虚拟机,通过编译器编译的.java文件会转换成.class的字节码文件,而不同操作系统的java虚拟机将字节码文件解释成对应的机器指令来执行。由此看来,java虚拟机并不认识.java文件,只能识别class文件,只要字节码文件是正确的就行。但是一般人还是写不来字节码文件的,我们打开一个字节码文件我们就知道其都是16进制数,并且字节码文件的写法有严格的要求。正因为字节码文件有着严格的结构,我们就可以利用它来写出标准的解析方法,甚至是生成我们指定的class类。ASM通过将这些解析和生成的方法封装,通过更加人性化的方法调用给我们提供供了解析和生成class类的方法。
ASM的范围:
The goal of the ASM library is to generate, transform and analyze compiled
Java classes, represented as byte arrays (as they are stored on disk and loaded
in the Java Virtual Machine). For this purpose ASM provides tools to read,
write and transform such byte arrays by using higher level concepts than bytes,
such as numeric constants, strings, Java identifiers, Java types, Java class
structure elements, etc. Note that the scope of the ASM library is strictly
limited to reading, writing, transforming and analyzing classes. In particular
the class loading process is out of scope.
ASM库的目标是生成、转换和分析编译后的Java类,这些类表示为字节数组(因为它
们存储在磁盘上,并加载在Java虚拟机中)。为此,ASM提供了一些工具,通过使用
比字节更高级别的概念(如数字常量、字符串、Java标识符、Java类型、Java类结构
元素等)来读取、写入和转换这样的字节数组。请注意,ASM库的范围严格限制于
读、写、转换和分析类。特别是类加载过程超出了范围。
the ASM name does not mean anything: it is just a reference to the __asm__ keyword in
C, which allows some functions to be implemented in assembly language.
ASM名称没有任何意义:它只是一个对……asm__关键字in的引用
它允许用汇编语言实现一些函数。
ASM库提供了两个api来生成和转换编译后的api类:
核心API提供了基于事件的类表示:基于事件的模型用一系列事件来表示类
tree API提供了一个基于对象的表示:基于对象的模型每个类都用对象树表示。
优缺点:
- 基于事件的API比基于对象的API更快,需要的内存更少,因为不需要在内存中创建和存储表示类的对象树(SAX和DOM之间也存在相同的差异)。
- 然而,使用基于事件的API实现类转换可能更加困难,因为在任何给定的时间内,该类中只有一个元素可用(对应于当前事件的元素),而使用基于对象的API,整个类在内存中可用。
Core API:
最主要的三个APIClassVistor,ClassReader,ClassWriter.
- ClassReader类解析作为字节数组给出的编译类,并在ClassVisitor上调用相visitXxx方法实例作为参数传递给它的accept方法。它可以被看作是一个事件生成器。
- ClassWriter类是ClassVisitor抽象类的子类,它直接以二进制形式构建编译后的类。它产生的输出一个字节数组,其中包含可检索的已编译类使用toByteArray方法。它可以被视为事件消费者。
- ClassVisitor类将它接收到的所有方法调用委托给另一个ClassVisitor实例。它可以看作是一个事件过滤器。
实例:(加载类信息并打印出来)
下面来门来通过一个简单的例子实现访问一个类,并打印出类的信息,这里主要通过CLassVisitor方法。我们继承ClassVisitor抽象类,并且重写里面的方法,前面已经提到了,CLassVisitor中一个方法就是描述一个类的信息。我们重写里面的方法将类的信息打印处理。我们将这些具体的行为封装在一哥类中,并把它交给CLassReader的accpet方法,accept方法会通过CLassVisitor中的方法完成具体的动作。这里用到了命令模式,将调用的方法封装起来,通过参数的形式传递。
class ClassPrinter extends ClassVisitor {
public ClassPrinter() {
super(Opcodes.ASM4);
}
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
System.out.println(name + " extends " + superName + " {");
}
public void visitSource(String source, String debug) {
}
public void visitOuterClass(String owner, String name, String desc) {
}
public AnnotationVisitor visitAnnotation(String desc,
boolean visible) {
return null;
}
public void visitAttribute(Attribute attr) {
}
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
}
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
System.out.println(" " + desc + " " + name);
return null;
}
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
System.out.println(" " + name + desc);
return null;
}
public void visitEnd() {
System.out.println("}");
}
}
现在我们需要将ClassVisitor传递给CLassReader的accept方法。ClassReader负责两指定的加载。
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Object");
cr.accept(cp, 0);
运行结果:
java/lang/Object extends null {
()V
registerNatives()V
getClass()Ljava/lang/Class;
hashCode()I
equals(Ljava/lang/Object;)Z
clone()Ljava/lang/Object;
toString()Ljava/lang/String;
notify()V
notifyAll()V
wait(J)V
wait(JI)V
wait()V
finalize()V
()V
}
我们再来看一下ClassReader类是如何加载指定的类的,我们发现其中调用了CLassLoader的gerResourceAsStream方法,我们再看看这个方法的作用,如下图,通过注释我们知道,这个方法会调用系统类加载器去加载指定的类。我们再看一下readStream方法,这个方法很简单,就是将inputstream读出来,返回byteArray。
public ClassReader(final String className) throws IOException {
this(
readStream(
ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
}
/**
* Open for reading, a resource of the specified name from the search path
* used to load classes. This method locates the resource through the
* system class loader (see {@link #getSystemClassLoader()}).
*
* @param name
* The resource name
*
* @return An input stream for reading the resource, or null
* if the resource could not be found
*
* @since 1.1
*/
public static InputStream getSystemResourceAsStream(String name) {
URL url = getSystemResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}
private static byte[] readStream(final InputStream inputStream, final boolean close)
throws IOException {
if (inputStream == null) {
throw new IOException("Class not found");
}
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE];
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
outputStream.write(data, 0, bytesRead);
}
outputStream.flush();
return outputStream.toByteArray();
} finally {
if (close) {
inputStream.close();
}
}
}
接下来我们再来看一下,accept中的方法是如何来实现解读方法的。
public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
accept(classVisitor, new Attribute[0], parsingOptions);
}
attributePrototypes:必须在类访问期间解析的属性的原型。任何类型不等于原型类型的属性都不会被解析:它的字节数组值将不加更改地传递给ClassWriter。如果这个值包含对常量池的引用,或者具有与类元素的语法或语义链接,而类元素是由读写器之间的类适配器转换的,那么这个值可能会破坏它。
parsingOptions:用于解析该类的选项。一个或多个{@link #SKIP_CODE}、{@link #SKIP_DEBUG}、{@link #SKIP_FRAMES}或{@link #EXPAND_FRAMES}。
这个方法很长,要理解这个方法的意思必须要了解class文件的构造,class的书写有严格的要求,其书写的顺序和大小都有着严格的要求,这里最主要的就是首先获得类的访问标识,是访问这个类结构的偏移量,有了这个访问标识,我们就相当于获得了类的入口,接下来我们根据class文件的要个规定,就可以按照指定的偏移量来访问类中的属性。
public void accept(
final ClassVisitor classVisitor,
final Attribute[] attributePrototypes,
final int parsingOptions) {
Context context = new Context();//被ClassReader解析的类信息
context.attributePrototypes = attributePrototypes;
context.parsingOptions = parsingOptions;
context.charBuffer = new char[maxStringLength];
//读取访问标识,类,父类,接口数量和接口,其中的currentOffset是类的访问标识
//符,其中这些字段信息间隔两个字节。将信息读取到content的charBuffer数组中。
char[] charBuffer = context.charBuffer;
int currentOffset = header;
int accessFlags = readUnsignedShort(currentOffset);
String thisClass = readClass(currentOffset + 2, charBuffer);
String superClass = readClass(currentOffset + 4, charBuffer);
String[] interfaces = new String[readUnsignedShort(currentOffset + 6)];
currentOffset += 8;
for (int i = 0; i < interfaces.length; ++i) {
interfaces[i] = readClass(currentOffset, charBuffer);
currentOffset += 2;
}
// Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS).
// Attribute offsets exclude the attribute_name_index and attribute_length fields.
// - The offset of the InnerClasses attribute, or 0.
int innerClassesOffset = 0;
// - The offset of the EnclosingMethod attribute, or 0.
int enclosingMethodOffset = 0;
// - The string corresponding to the Signature attribute, or null.
String signature = null;
// - The string corresponding to the SourceFile attribute, or null.
String sourceFile = null;
// - The string corresponding to the SourceDebugExtension attribute, or null.
String sourceDebugExtension = null;
// - The offset of the RuntimeVisibleAnnotations attribute, or 0.
int runtimeVisibleAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
int runtimeInvisibleAnnotationsOffset = 0;
// - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
int runtimeVisibleTypeAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
int runtimeInvisibleTypeAnnotationsOffset = 0;
// - The offset of the Module attribute, or 0.
int moduleOffset = 0;
// - The offset of the ModulePackages attribute, or 0.
int modulePackagesOffset = 0;
// - The string corresponding to the ModuleMainClass attribute, or null.
String moduleMainClass = null;
// - The string corresponding to the NestHost attribute, or null.
String nestHostClass = null;
// - The offset of the NestMembers attribute, or 0.
int nestMembersOffset = 0;
// - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
// This list in the reverse order or their order in the ClassFile structure.
Attribute attributes = null;
int currentAttributeOffset = getFirstAttributeOffset();
for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
// 读取属性信息的属性名字和属性长度字段。
String attributeName = readUTF8(currentAttributeOffset, charBuffer);
int attributeLength = readInt(currentAttributeOffset + 2);
currentAttributeOffset += 6;
// The tests are sorted in decreasing frequency order (based on frequencies observed on
// typical classes).
if (Constants.SOURCE_FILE.equals(attributeName)) {
sourceFile = readUTF8(currentAttributeOffset, charBuffer);
} else if (Constants.INNER_CLASSES.equals(attributeName)) {
innerClassesOffset = currentAttributeOffset;
} else if (Constants.ENCLOSING_METHOD.equals(attributeName)) {
enclosingMethodOffset = currentAttributeOffset;
} else if (Constants.NEST_HOST.equals(attributeName)) {
nestHostClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.NEST_MEMBERS.equals(attributeName)) {
nestMembersOffset = currentAttributeOffset;
} else if (Constants.SIGNATURE.equals(attributeName)) {
signature = readUTF8(currentAttributeOffset, charBuffer);
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset;
} else if (Constants.DEPRECATED.equals(attributeName)) {
accessFlags |= Opcodes.ACC_DEPRECATED;
} else if (Constants.SYNTHETIC.equals(attributeName)) {
accessFlags |= Opcodes.ACC_SYNTHETIC;
} else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
sourceDebugExtension =
readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
} else if (Constants.MODULE.equals(attributeName)) {
moduleOffset = currentAttributeOffset;
} else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
moduleMainClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.MODULE_PACKAGES.equals(attributeName)) {
modulePackagesOffset = currentAttributeOffset;
} else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
// The BootstrapMethods attribute is read in the constructor.
Attribute attribute =
readAttribute(
attributePrototypes,
attributeName,
currentAttributeOffset,
attributeLength,
charBuffer,
-1,
null);
attribute.nextAttribute = attributes;
attributes = attribute;
}
currentAttributeOffset += attributeLength;
}
// Visit the class declaration. The minor_version and major_version fields start 6 bytes before
// the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition).
classVisitor.visit(
readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces);
// Visit the SourceFile and SourceDebugExtenstion attributes.
if ((parsingOptions & SKIP_DEBUG) == 0
&& (sourceFile != null || sourceDebugExtension != null)) {
classVisitor.visitSource(sourceFile, sourceDebugExtension);
}
// Visit the Module, ModulePackages and ModuleMainClass attributes.
if (moduleOffset != 0) {
readModuleAttributes(
classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass);
}
// Visit the NestHost attribute.
if (nestHostClass != null) {
classVisitor.visitNestHost(nestHostClass);
}
// Visit the EnclosingMethod attribute.
if (enclosingMethodOffset != 0) {
String className = readClass(enclosingMethodOffset, charBuffer);
int methodIndex = readUnsignedShort(enclosingMethodOffset + 2);
String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer);
String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer);
classVisitor.visitOuterClass(className, name, type);
}
// Visit the RuntimeVisibleAnnotations attribute.
if (runtimeVisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleAnnotations attribute.
if (runtimeInvisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeVisibleTypeAnnotations attribute.
if (runtimeVisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
classVisitor.visitTypeAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleTypeAnnotations attribute.
if (runtimeInvisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
classVisitor.visitTypeAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the non standard attributes.
while (attributes != null) {
// Copy and reset the nextAttribute field so that it can also be used in ClassWriter.
Attribute nextAttribute = attributes.nextAttribute;
attributes.nextAttribute = null;
classVisitor.visitAttribute(attributes);
attributes = nextAttribute;
}
// Visit the NestedMembers attribute.
if (nestMembersOffset != 0) {
int numberOfNestMembers = readUnsignedShort(nestMembersOffset);
int currentNestMemberOffset = nestMembersOffset + 2;
while (numberOfNestMembers-- > 0) {
classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer));
currentNestMemberOffset += 2;
}
}
// Visit the InnerClasses attribute.
if (innerClassesOffset != 0) {
int numberOfClasses = readUnsignedShort(innerClassesOffset);
int currentClassesOffset = innerClassesOffset + 2;
while (numberOfClasses-- > 0) {
classVisitor.visitInnerClass(
readClass(currentClassesOffset, charBuffer),
readClass(currentClassesOffset + 2, charBuffer),
readUTF8(currentClassesOffset + 4, charBuffer),
readUnsignedShort(currentClassesOffset + 6));
currentClassesOffset += 8;
}
}
// Visit the fields and methods.
int fieldsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (fieldsCount-- > 0) {
currentOffset = readField(classVisitor, context, currentOffset);
}
int methodsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (methodsCount-- > 0) {
currentOffset = readMethod(classVisitor, context, currentOffset);
}
// Visit the end of the class.
classVisitor.visitEnd();
}
好了,到这里我们大致了解了整个流程,首先ClassReader通过系统类加载器来加载指定的类,返回的byte数组,我们再重写自己的ClassVisitor方法,传递给CLassReader的accept方法,CLassVisitor的具体方法会在accept中调用,accept方法通过我们的提供的CLassVIsitor方法来访问类信息,为了获得具体的类信息,就需要将类的字节码文件解读出来,这需要了解类的class文件的结构,通过获得类的class访问标识开始,就获得了访问class的入口。因为class文件有着严格的结构,所以我们可以通过具体的偏移量来解读到类信息。这就是上面的大致步骤。好了,今天的内容就是通过CLassReader和ClassVIsitor来获得指定的类并且按照我们的ClassVisitor来处理类,在以后的文章中我会介绍class文件的结构。