Type介绍

Type介绍,第1张

1. 为什么会存在Type类

在ASM的代码中,有一个Type类(org.objectweb.asm.Type)。为什么会有这样一个Type类呢?

大家知道,在JDK当中有一个java.lang.reflect.Type类。对于java.lang.reflect.Type类来说,它是一个接口,它有一个我们经常使用的子类,即java.lang.Class;相应的,在ASM当中有一个org.objectweb.asm.Type类。

JDKASM
类名java.lang.reflect.Typeorg.objectweb.asm.Type
位置rt.jarasm.jar

在编写代码层面,如果我们不能区分出java.lang.reflect.Type类和org.objectweb.asm.Type类,我们也不能很好的使用它们。

  • Java File:具体表现为.java文件,在里面使用Java语言编写代码,它是属于Java Language Specification的范畴。
  • Class File:具体表现为.class文件,它里面的内容遵循ClassFile的结构,它是属于JVM Specification的范畴。
  • ASM:它是一个类库。我们在编写ASM代码的时候,是在.java文件中编写,使用的是Java语言,而它所 *** 作的对象却是.class文件。

换句话说,ASM实现,从本质上来说,是一只脚踩在Java Language Specification的范畴,而另一只脚却踩在JVM Specification的范畴。ASM,在这两个范畴中,扮演的一个非常重要的角色,就是将Java Language Specification范畴的概念和JVM Specification范畴的概念进行转换。

这两个范畴,是相关的,但是又不是那种密不可分的关系。比如说,Java语言编写的程序可以运行在JVM上,Scala语言编写的程序也可以运行在JVM上,甚至Python语言编写的程序也可以编写在JVM上;也就是说,某一种编程语言和JVM之间,并不是一种非常强的依赖关系。

Java Language SpecificationASMJVM Specification
int<--- 向左转换 ---Type--- 向右转换 --->I
float<--- 向左转换 ---Type--- 向右转换 --->F
java.lang.String<--- 向左转换 ---Type--- 向右转换 --->java/lang/String

在.java文件中,我们经常使用java.lang.Class类;而在.class文件中,需要经常用到internal name、type descriptor和method descriptor;而在ASM中,org.objectweb.asm.Type类就是帮助我们进行两者之间的转换。

2. Type类 2.1. class info

第一个部分,Type类继承自Object类,而且带有final标识,所以不会存在子类。

public final class Type {
}
2.2. fields

第二个部分,Type类定义的字段有哪些。这里我们列出了4个字段,这4个字段可以分成两组。

  • 第一组,只包括sort字段,是int类型,它标识了Type类的类别。
  • 第二组,包括valueBuffer、valueBegin和valueEnd字段,这3个字段组合到一起表示一个value值,本质上就是一个字符串。
public final class Type {
    // 标识类型
    private final int sort;

    // 标识内容
    private final String valueBuffer;
    private final int valueBegin;
    private final int valueEnd;
}
2.3. constructors

第三个部分,Type类定义的构造方法有哪些。由于Type类的构造方法用private修饰,因此“外界”不能使用new关键字创建Type对象。

public final class Type {
    private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) {
        this.sort = sort;
        this.valueBuffer = valueBuffer;
        this.valueBegin = valueBegin;
        this.valueEnd = valueEnd;
    }
}
2.4. methods

第四个部分,Type类定义的方法有哪些。在Type类里,定义了一些方法,这些方法是与字段有直接关系的。

public final class Type {
    public int getSort() {
        return sort == INTERNAL ? OBJECT : sort;
    }

    public String getClassName() {
        switch (sort) {
            case VOID:
                return "void";
            case BOOLEAN:
                return "boolean";
            case CHAR:
                return "char";
            case BYTE:
                return "byte";
            case SHORT:
                return "short";
            case INT:
                return "int";
            case FLOAT:
                return "float";
            case LONG:
                return "long";
            case DOUBLE:
                return "double";
            case ARRAY:
                StringBuilder stringBuilder = new StringBuilder(getElementType().getClassName());
                for (int i = getDimensions(); i > 0; --i) {
                    stringBuilder.append("[]");
                }
                return stringBuilder.toString();
            case OBJECT:
            case INTERNAL:
                return valueBuffer.substring(valueBegin, valueEnd).replace('/', '.');
            default:
                throw new AssertionError();
        }
    }

    public String getInternalName() {
        return valueBuffer.substring(valueBegin, valueEnd);
    }

    public String getDescriptor() {
        if (sort == OBJECT) {
            return valueBuffer.substring(valueBegin - 1, valueEnd + 1);
        } else if (sort == INTERNAL) {
            return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';';
        } else {
            return valueBuffer.substring(valueBegin, valueEnd);
        }
    }
}

关于这些方法的使用,示例如下:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getType("Ljava/lang/String;");

        int sort = t.getSort();                    // ASM
        String className = t.getClassName();       // Java File
        String internalName = t.getInternalName(); // Class File
        String descriptor = t.getDescriptor();     // Class File

        System.out.println(sort);         // 10,它对应于Type.OBJECT字段
        System.out.println(className);    // java.lang.String   注意,分隔符是“.”
        System.out.println(internalName); // java/lang/String   注意,分隔符是“/”
        System.out.println(descriptor);   // Ljava/lang/String; 注意,分隔符是“/”,前有“L”,后有“;”
    }
}
3. 静态成员 3.1. 静态字段

在Type类里,定义了一些常量字段,有int类型,也有String类型。

public final class Type {
    public static final int VOID = 0;
    public static final int BOOLEAN = 1;
    public static final int CHAR = 2;
    public static final int BYTE = 3;
    public static final int SHORT = 4;
    public static final int INT = 5;
    public static final int FLOAT = 6;
    public static final int LONG = 7;
    public static final int DOUBLE = 8;
    public static final int ARRAY = 9;
    public static final int OBJECT = 10;
    public static final int METHOD = 11;
    private static final int INTERNAL = 12;

    private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD";
}

在Type类里,也定义了一些Type类型的字段,这些字段是由上面的int和String类型的字段组合得到。

public final class Type {
    public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1);
    public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1);
    public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1);
    public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1);
    public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1);
    public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1);
    public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1);
    public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1);
    public static final Type DOUBLE_TYPE = new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1);
}
3.2. 静态方法

这里介绍的几个get*Type()方法,是静态(static)方法。这几个方法的主要目的就是得到一个Type对象。

public final class Type {
    public static Type getType(final Class clazz) {
        if (clazz.isPrimitive()) {
            if (clazz == Integer.TYPE) {
                return INT_TYPE;
            } else if (clazz == Void.TYPE) {
                return VOID_TYPE;
            } else if (clazz == Boolean.TYPE) {
                return BOOLEAN_TYPE;
            } else if (clazz == Byte.TYPE) {
                return BYTE_TYPE;
            } else if (clazz == Character.TYPE) {
                return CHAR_TYPE;
            } else if (clazz == Short.TYPE) {
                return SHORT_TYPE;
            } else if (clazz == Double.TYPE) {
                return DOUBLE_TYPE;
            } else if (clazz == Float.TYPE) {
                return FLOAT_TYPE;
            } else if (clazz == Long.TYPE) {
                return LONG_TYPE;
            } else {
                throw new AssertionError();
            }
        } else {
            return getType(getDescriptor(clazz));
        }
    }

    public static Type getType(final Constructor<?> constructor) {
        return getType(getConstructorDescriptor(constructor));
    }

    public static Type getType(final Method method) {
        return getType(getMethodDescriptor(method));
    }

    public static Type getType(final String typeDescriptor) {
        return getTypeInternal(typeDescriptor, 0, typeDescriptor.length());
    }

    public static Type getMethodType(final String methodDescriptor) {
        return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length());
    }

    public static Type getObjectType(final String internalName) {
        return new Type(internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length());
    }
}
3.3. 获取Type对象

Type类有一个private的构造方法,因此Type对象实例不能通过new关键字来创建。但是,Type类提供了static method和static field来获取对象。

3.3.1. 方式一:java.lang.Class

从一个java.lang.Class对象来获取Type对象:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getType(String.class);
        System.out.println(t);
    }
}
3.3.2. 方式二:descriptor

从一个描述符(descriptor)来获取Type对象:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t1 = Type.getType("Ljava/lang/String;");
        System.out.println(t1);

        // 这里是方法的描述符
        Type t2 = Type.getMethodType("(II)I");
        System.out.println(t2);
    }
}
3.3.3. 方式三:internal name

从一个internal name来获取Type对象:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getObjectType("java/lang/String");
        System.out.println(t);
    }
}
3.3.4. 方式四:static field

从一个Type类的静态字段来获取Type对象:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.INT_TYPE;
        System.out.println(t);
    }
}
4. 特殊的方法 4.1. array-related methods

这里介绍的两个方法与数组类型相关:

  • getDimensions()方法,用于获取数组的维度
  • getElementType()方法,用于获取数组的元素的类型
public final class Type {
    public int getDimensions() {
        int numDimensions = 1;
        while (valueBuffer.charAt(valueBegin + numDimensions) == '[') {
            numDimensions++;
        }
        return numDimensions;
    }

    public Type getElementType() {
        final int numDimensions = getDimensions();
        return getTypeInternal(valueBuffer, valueBegin + numDimensions, valueEnd);
    }
}

示例代码:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getType("[[[[[Ljava/lang/String;");

        int dimensions = t.getDimensions();
        Type elementType = t.getElementType();

        System.out.println(dimensions);    // 5
        System.out.println(elementType);   // Ljava/lang/String;
    }
}
4.2. method-related methods

这里介绍的两个方法与“方法”相关:

  • getArgumentTypes()方法,用于获取“方法”接收的参数类型
  • getReturnType()方法,用于获取“方法”返回值的类型
public final class Type {
    public Type[] getArgumentTypes() {
        return getArgumentTypes(getDescriptor());
    }

    public Type getReturnType() {
        return getReturnType(getDescriptor());
    }
}

示例代码:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type methodType = Type.getMethodType("(Ljava/lang/String;I)V");

        String descriptor = methodType.getDescriptor();
        Type[] argumentTypes = methodType.getArgumentTypes();
        Type returnType = methodType.getReturnType();

        System.out.println("Descriptor: " + descriptor);
        System.out.println("Argument Types:");
        for (Type t : argumentTypes) {
            System.out.println("    " + t);
        }
        System.out.println("Return Type: " + returnType);
    }
}

输出结果:

Descriptor: (Ljava/lang/String;I)V
Argument Types:
    Ljava/lang/String;
    I
Return Type: V
4.3. size-related methods

这里列举的3个方法是与“类型占用slot空间的大小”相关:

  • getSize()方法,用于返回某一个类型所占用的slot空间的大小。
  • getArgumentsAndReturnSizes()方法,用于返回方法所对应的slot空间的大……
    • 其中,参数大小int argumentsSize = argumentsAndReturnSizes >> 2;。注意,在argumentsSize当中包含了隐藏的this变量。
    • 其中,返回值大小int returnSize = argumentsAndReturnSizes 0x03)
public final class Type {
    public int getSize() {
        switch (sort) {
            case VOID:
                return 0;
            case BOOLEAN:
            case CHAR:
            case BYTE:
            case SHORT:
            case INT:
            case FLOAT:
            case ARRAY:
            case OBJECT:
            case INTERNAL:
                return 1;
            case LONG:
            case DOUBLE:
                return 2;
            default:
                throw new AssertionError();
        }
    }

    public int getArgumentsAndReturnSizes() {
        return getArgumentsAndReturnSizes(getDescriptor());
    }

    public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
        int argumentsSize = 1;
        // Skip the first character, which is always a '('.
        int currentOffset = 1;
        int currentChar = methodDescriptor.charAt(currentOffset);
        // Parse the argument types and compute their size, one at a each loop iteration.
        while (currentChar != ')') {
            if (currentChar == 'J' || currentChar == 'D') {
                currentOffset++;
                argumentsSize += 2;
            } else {
                while (methodDescriptor.charAt(currentOffset) == '[') {
                    currentOffset++;
                }
                if (methodDescriptor.charAt(currentOffset++) == 'L') {
                    // Skip the argument descriptor content.
                    int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
                    currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
                }
                argumentsSize += 1;
            }
            currentChar = methodDescriptor.charAt(currentOffset);
        }
        currentChar = methodDescriptor.charAt(currentOffset + 1);
        if (currentChar == 'V') {
            return argumentsSize << 2;
        } else {
            int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1;
            return argumentsSize << 2 | returnSize;
        }
    }
}

示例代码:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.INT_TYPE;
        System.out.println(t.getSize()); // 1
    }
}
import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.LONG_TYPE;
        System.out.println(t.getSize()); // 2
    }
}
import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getMethodType("(II)I");
        int value = t.getArgumentsAndReturnSizes();

        int argumentsSize = value >> 2;
        int returnSize = value & 0b11;

        System.out.println(argumentsSize); // 3
        System.out.println(returnSize);    // 1
    }
}
4.4. opcode-related methods

这里介绍的方法与opcode相关:

  • getOpcode()方法,会让我们写代码的过程中更加方便。
public final class Type {
    public int getOpcode(final int opcode) {
        if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
            switch (sort) {
                case BOOLEAN:
                case BYTE:
                    return opcode + (Opcodes.BALOAD - Opcodes.IALOAD);
                case CHAR:
                    return opcode + (Opcodes.CALOAD - Opcodes.IALOAD);
                case SHORT:
                    return opcode + (Opcodes.SALOAD - Opcodes.IALOAD);
                case INT:
                    return opcode;
                case FLOAT:
                    return opcode + (Opcodes.FALOAD - Opcodes.IALOAD);
                case LONG:
                    return opcode + (Opcodes.LALOAD - Opcodes.IALOAD);
                case DOUBLE:
                    return opcode + (Opcodes.DALOAD - Opcodes.IALOAD);
                case ARRAY:
                case OBJECT:
                case INTERNAL:
                    return opcode + (Opcodes.AALOAD - Opcodes.IALOAD);
                case METHOD:
                case VOID:
                    throw new UnsupportedOperationException();
                default:
                    throw new AssertionError();
            }
        } else {
            switch (sort) {
                case VOID:
                    if (opcode != Opcodes.IRETURN) {
                        throw new UnsupportedOperationException();
                    }
                    return Opcodes.RETURN;
                case BOOLEAN:
                case BYTE:
                case CHAR:
                case SHORT:
                case INT:
                    return opcode;
                case FLOAT:
                    return opcode + (Opcodes.FRETURN - Opcodes.IRETURN);
                case LONG:
                    return opcode + (Opcodes.LRETURN - Opcodes.IRETURN);
                case DOUBLE:
                    return opcode + (Opcodes.DRETURN - Opcodes.IRETURN);
                case ARRAY:
                case OBJECT:
                case INTERNAL:
                    if (opcode != Opcodes.ILOAD && opcode != Opcodes.ISTORE && opcode != Opcodes.IRETURN) {
                        throw new UnsupportedOperationException();
                    }
                    return opcode + (Opcodes.ARETURN - Opcodes.IRETURN);
                case METHOD:
                    throw new UnsupportedOperationException();
                default:
                    throw new AssertionError();
            }
        }
    }
}

示例代码:

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.Printer;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.FLOAT_TYPE;

        int[] opcodes = new int[]{
                Opcodes.IALOAD,
                Opcodes.IASTORE,
                Opcodes.ILOAD,
                Opcodes.ISTORE,
                Opcodes.IADD,
                Opcodes.ISUB,
                Opcodes.IRETURN,
        };

        for (int oldOpcode : opcodes) {
            int newOpcode = t.getOpcode(oldOpcode);

            String oldName = Printer.OPCODES[oldOpcode];
            String newName = Printer.OPCODES[newOpcode];

            System.out.printf("%-7s --- %-7s%n", oldName, newName);
        }
    }
}

输出结果:

IALOAD  --- FALOAD
IASTORE --- FASTORE
ILOAD   --- FLOAD
ISTORE  --- FSTORE
IADD    --- FADD
ISUB    --- FSUB
IRETURN --- FRETURN
5. 总结
  • 第一点,Type类的作用是什么?Type类是一个工具类,它的一个主要目的是将Java语言当中的概念转换成ClassFile当中的概念。
  • 第二点,学习Type类的方式就是“分而治之”。在Type类当中,定义了许多的字段和方法,它们是一个整体,内容也很繁杂;于是,我们将Type类分成不同的部分来讲解,就是希望大家能循序渐进的理解这个类的各个部分,方便以后对该类的使用。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/921493.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存