第二章 用Java实现JVM之解析Class文件

3 篇文章 2 订阅

用Java实现JVM目录

第零章 用Java实现JVM之随便说点什么
第一章 用Java实现JVM之JVM的准备知识
第二章 用Java实现JVM之解析Class文件
第三章 用Java实现JVM之运行时数据区
第四章 用Java实现JVM之指令集和解释器
第五章 用Java实现JVM之类和对象
第六章 用Java实现JVM之方法调用
第七章 用Java实现JVM之数组和字符串
第八章 用Java实现JVM之本地方法调用
第九章 用Java实现JVM之异常处理
第十章 用Java实现JVM之结束



前言

  前边已经具体讨论过jvm的规范了,终于开始lu代码了,接下来就开始吧

在这里插入图片描述


一、读取class文件

   Java虚拟机规范对class文件格式进行了严格的规定。但是另一方面,对于从哪里加载class文件,给了足够多的自由。可以从各种途径中读取class文件,以十六进制的方式存储到内存。首先定义一个类,用来保存class文件信息(包含字节码、类名等信息),代码如下:

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * 类文件信息
 *
 * @author hqd
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ClassFileInfo {
    /**
     * class十六进制字符串
     */
    private String byteCode;
    /**
     * java全类名
     */
    private String classPath;
    /**
     * 文件路径
     */
    private String path;
    /**
     * 文件名
     */
    private String fileName;

    public ClassFileInfo(String byteCode) {
        this.byteCode = byteCode;
    }

    public ClassFileInfo(String classPath, String path, String fileName) {
        this.classPath = classPath;
        this.path = path;
        this.fileName = fileName;
    }
}

接下来再来看读取class,由于可以从多途径进行读取,所以我们这里先定义一个接口,然后各种途径读取由各个子类具体实现,代码如下:


import java.io.IOException;
import java.util.List;

/**
 * class读取接口
 *
 * @author hqd
 */
public interface ClassResource {
    /**
     * 文件后缀名
     */
    String CLASS_FILE_SUFFIX = ".class";
    String JAR_FILE_SUFFIX = ".jar";
    String ZIP_FILE_SUFFIX = ".zip";
    /**
     * 通配符
     */
    String WILDCARD_CHARACTER = "*";

    /**
     * 读取所有class信息
     *
     * @return
     * @throws IOException
     */
    List<ClassFileInfo> readAllClass() throws IOException;

    /**
     * 读取一个class信息
     *
     * @param classNamePath
     * @return
     * @throws IOException
     */
    ClassFileInfo readClass(String classNamePath) throws IOException;

    /**
     * 是否已经读取class
     *
     * @param classPath
     * @return
     */
    boolean isExists(String classPath);
}

这里定义了三种文件类型,分别是zip、jar、class。定义完接口之后,我们思考下,对于这些文件类型,是否有公共的地方?可以发现,搜索对应的文件、读取class文件都是公共的,所以我们再抽取一个抽象类,子类如果有需要,可以重写父类的方法。代码如下:

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.ArrayUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * 加载class的抽象类
 *
 * @author hqd
 */
@Getter
@Setter
public abstract class AbstractClassResource implements ClassResource {
    private Map<String, ClassFileInfo> classHexMap;
    private List<File> classFileList;
    private String rootPath;
    private String fileSuffix;

    public AbstractClassResource(String rootPath, String fileSuffix) {
        this.classHexMap = new HashMap<>();
        this.classFileList = new LinkedList<>();
        this.rootPath = rootPath;
        this.fileSuffix = fileSuffix;
        init();
    }


    protected void init() {
        this.classFileList = searchFiles(getRootPath(), getFileSuffix(), false);
    }

    protected boolean isWildcardCharacter(String path) {
        return path.endsWith(WILDCARD_CHARACTER) ? true : false;
    }

    /**
     * 读取class文件
     *
     * @param in
     * @return
     */
    protected String readClassFileToStr(InputStream in) {
        try (BufferedInputStream bis = new BufferedInputStream(in)) {
            int code;
            StringBuilder sb = new StringBuilder();
            while ((code = bis.read()) != -1) {
                String temp = Integer.toHexString(code);
                sb.append(temp.length() < 2 ? "0" + temp : temp);
            }
            return sb.toString();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据后缀名搜索文件
     *
     * @param path
     * @param fileSuffix
     * @param isRecursion
     * @return
     */
    protected List<File> searchFiles(String path, String fileSuffix, boolean isRecursion) {
        List<File> list = new LinkedList<>();
        File file = new File(path);
        if (file.isFile()) {
            if (file.getName().endsWith(fileSuffix)) {
                list.add(file);
            }
        } else {
            File[] files = file.listFiles();
            if (ArrayUtils.isNotEmpty(files)) {
                for (File t : files) {
                    if (t.isFile()) {
                        if (t.getName().endsWith(fileSuffix)) {
                            list.add(t);
                        }
                    } else {
                        if (isRecursion) {
                            list.addAll(searchFiles(t.getPath(), fileSuffix, true));
                        }
                    }
                }
            }
        }
        return list;
    }


    @Override
    public boolean isExists(String classPath) {
        return getClassHexMap().get(classPath) == null ? false : true;
    }
}

抽取完抽象类之后,再来看下各个途径的具体实现吧,首先是读取文件夹下的所有class文件。这个比较简单些,只需要递归的所有文件夹下的所有class文件进行读取即可。代码如下:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 加载路径下的class文件
 *
 * @author hqd
 */
public class DirClassResource extends AbstractClassResource {

    public DirClassResource(String rootPath) {
        super(rootPath, CLASS_FILE_SUFFIX);
    }

    @Override
    protected void init() {
        setClassFileList(searchFiles(getRootPath(), CLASS_FILE_SUFFIX, true));
    }

    @Override
    public List<ClassFileInfo> readAllClass() throws IOException {
        for (File file : getClassFileList()) {
            ClassFileInfo classFileInfo = new ClassFileInfo(readClassFileToStr(new FileInputStream(file)), getRootPath(), file.getName());
            getClassHexMap().put(file.getPath(), classFileInfo);
        }
        return new ArrayList<>(getClassHexMap().values());
    }

    @Override
    public ClassFileInfo readClass(String filePath) throws IOException {
        File classFile = new File((getRootPath() + "/" + filePath).intern());
        ClassFileInfo classFileInfo = null;
        if (classFile.isFile() && classFile.getName().endsWith(CLASS_FILE_SUFFIX)) {
            if (getClassHexMap().get(classFile.getPath()) == null) {
                classFileInfo = new ClassFileInfo(readClassFileToStr(new FileInputStream(classFile)), filePath, classFile.getPath(), classFile.getName());
                getClassHexMap().put(classFile.getPath(), classFileInfo);
            }
            classFileInfo = getClassHexMap().get(classFile.getPath());
        }
        return classFileInfo;
    }

    @Override
    public boolean isExists(String classPath) {
        return new File(getRootPath() + "/" + classPath).exists();
    }
}

jar的读取方式和zip一致,所以,先来看下zip文件的读取方式,第一步还是一样的,首先先搜索文件夹下所有的zip文件,但是读取方式就不一样了。这里我们只读取了class文件,这里借用Apache的工具类进行读取。代码如下:


import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;

import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipInputStream;

/**
 * 加载路径下的zip文件
 *
 * @author hqd
 */
public class ZipClassResource extends AbstractClassResource {


    public ZipClassResource(String rootPath, String fileSuffix) {
        super(rootPath, fileSuffix);
    }

    public ZipClassResource(String rootPath) {
        this(rootPath, ZIP_FILE_SUFFIX);
    }

    @Override
    protected void init() {
        super.init();
        for (File file : getClassFileList()) {
            ZipFile zipFile = null;
            try {
                zipFile = new ZipFile(file.getPath());
                Enumeration<ZipEntry> entries = zipFile.getEntries();
                while (entries.hasMoreElements()) {
                    ZipEntry zipEntry = entries.nextElement();
                    if (!zipEntry.isDirectory() && zipEntry.getName().endsWith(CLASS_FILE_SUFFIX)) {
                        ClassFileInfo classFileInfo = new ClassFileInfo();
                        classFileInfo.setClassPath(zipEntry.getName());
                        classFileInfo.setFileName(file.getName());
                        classFileInfo.setPath(file.getPath());
                        getClassHexMap().put(zipEntry.toString(), classFileInfo);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                if (zipFile != null) {
                    try {
                        zipFile.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 获取zip包中class文件的二进制流
     *
     * @param classNamePath
     * @return
     * @throws IOException
     */
    protected ClassFileInfo parseClassInZip(String classNamePath) throws IOException {
        ClassFileInfo classFileInfo = getClassHexMap().get(classNamePath);
        if (classFileInfo != null) {
            ZipFile zf = new ZipFile(classFileInfo.getPath());
            InputStream in = null;
            ZipInputStream zin = null;
            try {
                getClassHexMap().get(classNamePath).setByteCode(super.readClassFileToStr(zf.getInputStream(zf.getEntry(classNamePath))));
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                if (in != null) {
                    in.close();
                }
                if (zf != null) {
                    zf.close();
                }
                if (zin != null) {
                    zin.closeEntry();
                }
            }
        }
        return classFileInfo;
    }

    protected void parseAllClassInZip(File file) throws IOException {
        ZipFile zf = new ZipFile(file.getPath());
        InputStream in = null;
        ZipInputStream zin = null;
        try {
            in = new BufferedInputStream(new FileInputStream(file.getPath()));
            zin = new ZipInputStream(in);
            Enumeration<ZipEntry> entries = zf.getEntries();
            while (entries.hasMoreElements()) {
                ZipEntry zipEntry = entries.nextElement();
                if (zipEntry.toString().endsWith(CLASS_FILE_SUFFIX)) {
                    getClassHexMap().get(zipEntry.getName()).setByteCode(super.readClassFileToStr(zf.getInputStream(zipEntry)));
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (in != null) {
                in.close();
            }
            if (zf != null) {
                zf.close();
            }
            if (zin != null) {
                zin.closeEntry();
            }
        }
    }

    @Override
    public List<ClassFileInfo> readAllClass() throws IOException {
        parseAllClassInZip(new File(getRootPath()));
        return new ArrayList<>(getClassHexMap().values());
    }


    @Override
    public ClassFileInfo readClass(String classNamePath) throws IOException {

        return parseClassInZip(classNamePath.intern());
    }
}

zip读取方式实现后,jar文件就简单了,和zip的区别只有搜索文件的后缀名不同,直接继承就行了,代码如下:

/**
 * 加载jar包文件
 *
 * @author hqd
 */
public class JarClassResource extends ZipClassResource {
    public JarClassResource(String rootPath) {
        super(rootPath, JAR_FILE_SUFFIX);
    }
}

最后再定一个类用来屏蔽读取细节,代码如下:

import com.hqd.jjvm.classformat.ClassFile;
import com.hqd.jjvm.util.ClassUtil;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 类读取器
 *
 * @author hqd
 */
@NoArgsConstructor
@Getter
public class ClassParse {

    private ClassResource classResource;

    public ClassParse(ClassResource classResource) {
        this.classResource = classResource;
    }

    public boolean isExists(String className) {
        if (!className.endsWith(ClassResource.CLASS_FILE_SUFFIX)) {
            className = className.replaceAll("\\.", "/");
        } else {
            className = ClassUtil.getPackagePath(className);
        }
        className += ClassResource.CLASS_FILE_SUFFIX;
        return classResource.isExists(className);
    }

    public Map<String, ClassFile> parseAll() throws IOException {
        List<ClassFileInfo> list = classResource.readAllClass();
        Map<String, ClassFile> classFileMap = new HashMap<>(list.size());
        for (ClassFileInfo classFileInfo : list) {
            ClassFile classFile = new ClassFile(classFileInfo);
            String className = classFileInfo.getFileName().substring(0, classFileInfo.getFileName().lastIndexOf("."));
            classFileMap.put(className, classFile);
        }
        return classFileMap;
    }

    public ClassFile parseClass(String className) throws IOException {
        className = className.replaceAll("\\.", "/");
        ClassFileInfo classFileInfo = classResource.readClass(className + ClassResource.CLASS_FILE_SUFFIX);
        if (classFileInfo != null) {
            return new ClassFile(classFileInfo);
        }
        return null;

    }
}

好了,类读取就说完了,其实这里的接口定义的不是很好,数据库、网络等来源就没法实现,可以进行更高层的抽象,这里就不去弄了,有兴趣的小伙伴可以自己试试

在这里插入图片描述

二、解析class文件

   读取完class文件,我们就需要把那堆十六进制的字符串转成数据结构(也就是对象),才能给我们使用。Java虚拟机规范定义了u1、u2和u4三种数据类型来表示1、2和4字节无符号整数,我们把这些方法都抽取成对应的抽象类,如下:


import com.hqd.jjvm.classpath.ClassFileInfo;
import lombok.Getter;

/**
 * @author hqd
 * 基础class信息
 */
@Getter
public abstract class AbstractClassCode {
    private ClassFileInfo classFileInfo;
    private String hexStr;

    public AbstractClassCode(ClassFileInfo classFileInfo) {
        this.classFileInfo = classFileInfo;
        this.hexStr = classFileInfo.getByteCode();
    }

    /**
     * 是否以只读的方式(不移动指针),读取指定长度字节
     *
     * @param length
     * @param isReadOnly
     * @return
     */
    public String read(int length, boolean isReadOnly) {
        String str = this.hexStr.substring(0, length << 1);
        if (!isReadOnly) {
            this.hexStr = this.hexStr.substring(length << 1);
        }
        return str;
    }

    /**
     * 读取指定长度字节
     *
     * @param length
     * @return
     */
    public String read(int length) {
        return read(length, false);
    }

    /**
     * 读取1个字节
     *
     * @return
     */
    public String readU1() {
        return read(1);
    }

    /**
     * 读取2个字节
     *
     * @return
     */
    public String readU2() {
        return read(2);
    }

    /**
     * 读取4个字节
     *
     * @return
     */
    public String readU4() {
        return read(4);
    }

    /**
     * 读取8个字节
     *
     * @return
     */
    public String readU8() {
        return read(8);
    }
}

接下来就是定义对应ClassFile类,与Class规范的文件结构一致,如下:



import com.hqd.jjvm.classformat.attributeinfo.AttributeInfo;
import com.hqd.jjvm.classformat.attributeinfo.AttributeInfoFactory;
import com.hqd.jjvm.classformat.constantpool.*;
import com.hqd.jjvm.classpath.ClassFileInfo;
import com.hqd.jjvm.util.HexStrTransformUtil;
import lombok.Getter;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * 类结构信息
 *
 * @author hqd
 */
@Getter
public class ClassFile extends AbstractClassCode {
    /**
     * 校验器
     */
    private JClassChecker classChecker;

    /**
     * 字节码版本
     */
    private ClassVersion version;
    /**
     * 常量池大小
     */
    private Integer constantPoolCount;
    /**
     * 常量池信息
     */
    private List<ConstantPool> constantPools;
    /**
     * 访问标识
     */
    private Integer accessFlags;
    /**
     * 类名索引
     */
    private Integer thisClassIndex;
    /**
     * 父类索引
     */
    private Integer superClassIndex;
    /**
     * 接口数量
     */
    private Integer interfaceCount;
    /**
     * 接口信息
     */
    private List<ConstantClassInfo> interfaces;
    /**
     * 字段数量
     */
    private Integer fieldCount;
    /**
     * 字段信息
     */
    private List<FieldInfo> fieldInfos;
    /**
     * 方法数量
     */
    private Integer methodCount;
    /**
     * 方法信息
     */
    private List<MethodInfo> methodInfos;
    /**
     * 属性数量
     */
    private Integer attributeCount;
    /**
     * 属性信息
     */
    private List<AttributeInfo> attributeInfos;

    public ClassFile(ClassFileInfo classFileInfo) {
        super(classFileInfo);
        this.classChecker = new JClassChecker(this);
        parseClassFile();
    }


    protected void parseClassFile() {
        parseClassVersion();
        parseConstantPool();
        parseClassAccessFlags();
        parseThisClassIndex();
        parseSuperClassIndex();
        parseInterfaces();
        parseFieldInfo();
        parseMethodInfo();
        parseAttributeInfo();
        classChecker.checkClassFileEnd();
    }


    private void parseAttributeInfo() {
        this.attributeCount = HexStrTransformUtil.parseHexToInt(readU2());
        this.attributeInfos = parseAttributeInfo(attributeCount);
    }

    /**
     * 解析属性列表
     *
     * @param count
     * @return
     */
    public List<AttributeInfo> parseAttributeInfo(int count) {
        List<AttributeInfo> attributeInfos = new LinkedList<>();
        for (int i = 0; i < count; i++) {
            int attributeNameIndex = HexStrTransformUtil.parseHexToInt(read(2, true));
            ConstantUtf8Info constantUtf8Info = (ConstantUtf8Info) constantPools.get(attributeNameIndex);
            String type = constantUtf8Info.getBytes();
            AttributeType attributeType = AttributeType.getTypeByVal(type);
            AttributeInfo attributeInfo = AttributeInfoFactory.newAttributeInfo(this, attributeType);
            attributeInfos.add(attributeInfo);
        }
        return attributeInfos;
    }

    /**
     * 转换方法信息
     */
    private void parseMethodInfo() {
        this.methodCount = HexStrTransformUtil.parseHexToInt(readU2());
        this.methodInfos = new ArrayList<>(methodCount);
        for (int i = 0; i < this.methodCount; i++) {
            MethodInfo methodInfo = new MethodInfo();
            methodInfo.setAccessFlags(HexStrTransformUtil.parseHexToInt(readU2()));
            methodInfo.setNameIndex(HexStrTransformUtil.parseHexToInt(readU2()));
            methodInfo.setDescriptorIndex(HexStrTransformUtil.parseHexToInt(readU2()));
            int attributeCount = HexStrTransformUtil.parseHexToInt(readU2());
            methodInfo.setAttributeCount(attributeCount);
            methodInfo.setAttributeInfos(parseAttributeInfo(attributeCount));
            this.methodInfos.add(methodInfo);
        }
    }

    /**
     * 转换字段信息
     */
    private void parseFieldInfo() {
        this.fieldCount = HexStrTransformUtil.parseHexToInt(readU2());
        this.fieldInfos = new ArrayList<>(fieldCount);
        for (int i = 0; i < this.fieldCount; i++) {
            FieldInfo fieldInfo = new FieldInfo();
            fieldInfo.setAccessFlags(HexStrTransformUtil.parseHexToInt(readU2()));
            fieldInfo.setNameIndex(HexStrTransformUtil.parseHexToInt(readU2()));
            fieldInfo.setDescriptorIndex(HexStrTransformUtil.parseHexToInt(readU2()));
            Integer attrCount = HexStrTransformUtil.parseHexToInt(readU2());
            fieldInfo.setAttributeCount(attrCount);
            fieldInfo.setAttributeInfos(parseAttributeInfo(attrCount));
            this.fieldInfos.add(fieldInfo);
        }
    }

    /**
     * 读取实现接口信息
     */
    private void parseInterfaces() {
        this.interfaceCount = HexStrTransformUtil.parseHexToInt(readU2());
        this.interfaces = new ArrayList<>(interfaceCount);
        for (int i = 0; i < interfaceCount; i++) {
            Integer index = HexStrTransformUtil.parseHexToInt(readU2());
            this.interfaces.add((ConstantClassInfo) this.constantPools.get(index));
        }
    }

    /**
     * 读取父类名
     */
    private void parseSuperClassIndex() {
        this.superClassIndex = HexStrTransformUtil.parseHexToInt(readU2());
    }

    /**
     * 读取类名
     */
    private void parseThisClassIndex() {
        this.thisClassIndex = HexStrTransformUtil.parseHexToInt(readU2());
    }

    /**
     * 读取类标识
     */
    private void parseClassAccessFlags() {
        this.accessFlags = HexStrTransformUtil.parseHexToInt(readU2());
    }

    /**
     * 读取常量池
     */
    private void parseConstantPool() {
        /**
         * 第一个是空,和常量池对应
         */
        this.constantPoolCount = HexStrTransformUtil.parseHexToInt(readU2());
        this.constantPools = new ArrayList<>(constantPoolCount);
        this.constantPools.add(new ConstantPool(""));
        for (int i = 1; i < constantPoolCount; ) {
            ConstantInfoType tag = ConstantInfoType.getType(Integer.parseUnsignedInt(readU1(), 16));
            List<ConstantPool> constantPoolList = ConstantPoolFactory.newConstantPool(tag, this);
            this.constantPools.addAll(constantPoolList);
            i += constantPoolList.size();
        }
    }

    /**
     * 读取魔数和版本号
     */
    private void parseClassVersion() {
        //魔数
        String magic = readU4();
        classChecker.checkMagicNumber(magic);
        //minor_version 副版本号
        float minorVersion = HexStrTransformUtil.parseHexToFloat(readU2());
        //major_version 主版本号
        float majorVersion = HexStrTransformUtil.parseHexToFloat(readU2());
        classChecker.checkClassVersion(majorVersion, minorVersion);
        this.version = new ClassVersion(magic, minorVersion, majorVersion);
    }
}

这里虽然代码比较多,但是都是按照虚拟机规范读取的(这里多了个类校验器后边再说

最后再来个工具类,将十六进制的字符串转成数字,代码如下:

import org.apache.commons.lang.StringUtils;

/**
 * 十六进制字符串转换器
 *
 * @author hqd
 */
public class HexStrTransformUtil {
    public static Double parseHexToDouble(String hexStr) {
        if (StringUtils.isNotBlank(hexStr)) {
            return Double.longBitsToDouble(parseHexToLong(hexStr));
        }
        return null;
    }

    public static Long parseHexToLong(String hexStr) {
        if (StringUtils.isNotBlank(hexStr)) {
            return Long.parseUnsignedLong(hexStr, 16);
        }
        return null;
    }

    public static Integer parseHexToInt(String hexStr) {
        if (StringUtils.isNotBlank(hexStr)) {
            return Integer.parseUnsignedInt(hexStr, 16);
        }
        return null;
    }

    public static Float parseHexToFloat(String hexStr) {
        if (StringUtils.isNotBlank(hexStr)) {
            return Float.valueOf(parseHexToInt(hexStr));
        }
        return null;
    }

    public static String pareHexToStr(String hexStr) {
        if (StringUtils.isNotBlank(hexStr)) {
            byte[] bytes = new byte[hexStr.length()];
            for (int i = 0; i < hexStr.length() >> 1; i++) {
                bytes[i] = (byte) (0xff & Integer.parseInt(hexStr.substring(i * 2, i * 2 + 2), 16));
            }
            return new String(bytes).trim();
        }
        return "";
    }
}

   接着我们再来看看Class文件的每一项信息
在这里插入图片描述

1、魔数、版本

      首先是魔数和版本的读取,我们知道魔数是固定的,占4个字节,所以是readU4(),之后是副版本号,占2个字节,所以是readU2(),接下来的主版本也是一样的
在这里插入图片描述

2、常量池

      接着就是常量池的信息,先出现的是常量池的个数(2个字节),再者就是常量池的各项信息
在这里插入图片描述

按照虚拟机规范,常量池分为很多种,但是有通用格式,如下:
在这里插入图片描述

所以老样子,我们定一个常量池基础类,代码如下:


import com.hqd.jjvm.classformat.ClassFile;
import com.hqd.jjvm.util.HexStrTransformUtil;
import lombok.Getter;

/**
 * 常量池类
 */
@Getter
public class ConstantPool {
	protected ConstantInfoType tag;
	protected ClassFile classFile;

	public ConstantPool(String tagStr) {
		this.tag = ConstantInfoType.getType(HexStrTransformUtil.parseHexToInt(tagStr));
	}

	public ConstantPool(ConstantInfoType tag, ClassFile classFile) {
		this.tag = tag;
		this.classFile = classFile;
	}

	public Object getVal() {
		return null;
	}

	/**
	 * 检查下标是否越界
	 *
	 * @param index
	 */
	protected void checkPoolIndex(int index) {
		if (index < 0 || index > classFile.getConstantPoolCount()) {
			throw new ClassFormatError(String.format(" Invalid constant pool index %s in class file %s ", index, classFile.getClassFileInfo().getFileName()));
		}
	}

}

再定义一个枚举类,用来映射常量池类型,代码如下:

/**
 * 常量池类型
 *
 * @author hqd
 */

public enum ConstantInfoType {
    CONSTANT_UTF8(1),
    CONSTANT_INTEGER(3),
    CONSTANT_FLOAT(4),
    CONSTANT_LONG(5),
    CONSTANT_DOUBLE(6),
    CONSTANT_CLASS(7),
    CONSTANT_STRING(8),
    CONSTANT_FIELDREF(9),
    CONSTANT_METHODREF(10),
    CONSTANT_INTERFACEMETHODREF(11),
    CONSTANT_NAMEANDTYPE(12),
    CONSTANT_METHODHANDLE(15),
    CONSTANT_METHODTYPE(16),
    CONSTANT_INVOKEDYNAMIC(18);

    private int tag;

    ConstantInfoType(int tag) {
        this.tag = tag;
    }

    public int getTag() {
        return tag;
    }

    public static ConstantInfoType getType(Integer tag) {
        if (tag == null) {
            return null;
        }
        ConstantInfoType[] types = ConstantInfoType.values();
        for (ConstantInfoType type : types) {
            if (type.tag == tag) {
                return type;
            }
        }
        return null;
    }
}

这里类型比较多就不一一展开了,就以一个CONSTANT_utf8_info字符串常量)为例吧,CONSTANT_utf8_info的结构如下:
在这里插入图片描述
代码如下:


import com.hqd.jjvm.classformat.ClassFile;
import com.hqd.jjvm.util.HexStrTransformUtil;
import lombok.Getter;

/**
 * 字符串常量池
 */
@Getter
public class ConstantUtf8Info extends ConstantPool {
	private String bytes;
	private Integer length;

	public ConstantUtf8Info(ConstantInfoType tag, ClassFile classFile) {
		super(tag, classFile);
		this.length = HexStrTransformUtil.parseHexToInt(classFile.readU2());
		this.bytes = HexStrTransformUtil.pareHexToStr(classFile.read(length));
	}

	@Override
	public String getVal() {
		return bytes.trim().intern();
	}
}

其他常量池类型都是类似,接着由于有这么多常量池类型,所以再定义个简单工厂,用于创建各个不同类型的常量池信息。也没啥稀奇的,就是通过判断tag,创建对应的常量池信息实例对象并返回,代码如下:


import com.hqd.jjvm.classformat.ClassFile;

import java.util.LinkedList;
import java.util.List;

/**
 * 常量池工厂
 *
 * @author hqd
 */
public class ConstantPoolFactory {
    public static List<ConstantPool> newConstantPool(ConstantInfoType tag, ClassFile classFile) {
        List<ConstantPool> constantPoolList = new LinkedList<>();
        switch (tag) {
            case CONSTANT_UTF8:
                constantPoolList.add(new ConstantUtf8Info(tag, classFile));
                break;
            case CONSTANT_INTEGER:
                constantPoolList.add(new ConstantIntegerInfo(tag, classFile));
                break;
            case CONSTANT_FLOAT:
                constantPoolList.add(new ConstantFloatInfo(tag, classFile));
                break;
            case CONSTANT_FIELDREF:
                constantPoolList.add(new ConstantFieldRefInfo(tag, classFile));
                break;
            case CONSTANT_METHODREF:
                constantPoolList.add(new ConstantMethodRefInfo(tag, classFile));
                break;
            case CONSTANT_INTERFACEMETHODREF:
                constantPoolList.add(new ConstantInterfaceMethodRefInfo(tag, classFile));
                break;
            case CONSTANT_NAMEANDTYPE:
                constantPoolList.add(new ConstantNameAndTypeInfo(tag, classFile));
                break;
            case CONSTANT_INVOKEDYNAMIC:
                constantPoolList.add(new ConstantInvokedynamicInfo(tag, classFile));
                break;
            case CONSTANT_METHODHANDLE:
                constantPoolList.add(new ConstantMethodhandleInfo(tag, classFile));
                break;
            case CONSTANT_LONG:
                constantPoolList.add(new ConstantLongInfo(tag, classFile));
                constantPoolList.add(new ConstantPool(""));
                break;
            case CONSTANT_DOUBLE:
                constantPoolList.add(new ConstantDoubleInfo(tag, classFile));
                constantPoolList.add(new ConstantPool(""));
                break;
            case CONSTANT_CLASS:
                constantPoolList.add(new ConstantClassInfo(tag, classFile));
                break;
            case CONSTANT_STRING:
                constantPoolList.add(new ConstantStringInfo(tag, classFile));
                break;
            case CONSTANT_METHODTYPE:
                constantPoolList.add(new ConstantMethodType(tag, classFile));
                break;
            default:
                throw new ClassFormatError("无效常量池类型");
        }
        return constantPoolList;
    }
}

3、类标识

      这个没啥好说的,固定2个字节,如下:
在这里插入图片描述

4、当前类、父类

      这两个也没啥东西,指向常量池下标,固定读取2个字节
在这里插入图片描述
在这里插入图片描述

4、接口

      接着就是接口的信息,先出现的是接口的个数(2个字节),再者就是每个接口所对应的常量池下标(每个下标2个字节)。我们只要按照接口的个数,依次按2个字节往下读取即可,如下:
在这里插入图片描述

5、字段

      接着是字段信息,先出现的是字段的个数(2个字节),后边跟着的是每个字段的具体信息,这里由于字段和方法有些公共的信息,所以抽取一个基础类,代码如下:

import com.hqd.jjvm.classformat.attributeinfo.AttributeInfo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;

/**
 * 类元信息
 *
 * @author hqd
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ClassMemberInfo {
    private Integer accessFlags;
    private Integer nameIndex;
    private Integer descriptorIndex;
    private Integer attributeCount;
    private List<AttributeInfo> attributeInfos;
}

字段的信息只需直接继承即可,代码如下:


import lombok.AllArgsConstructor;

/**
 * 字段信息
 *
 * @author hqd
 */
@AllArgsConstructor
public class FieldInfo extends ClassMemberInfo {
}

有了实体类,接下来只需要给实体类塞值就行了,这里最后会涉及到属性的解析,在第7节会说,这里先按下不表。如下:
在这里插入图片描述

6、方法

      方法信息的解析和字段信息类似,先定义方法实体类,代码如下:

import lombok.AllArgsConstructor;

/**
 * 方法信息
 *
 * @author hqd
 */
@AllArgsConstructor
public class MethodInfo extends ClassMemberInfo {
}

其他都是一样的,这里就不多赘述了。如下:
在这里插入图片描述

7、属性

      由于属性很多地方都是涉及到(类、方法、字段等),这里重点看一下属性的解析吧

      属性和常量池一样,都有很多种类型,但是又有基础的信息,如下:
在这里插入图片描述
所以我们照旧定义基础类,代码如下:

import com.hqd.jjvm.classformat.ClassFile;
import com.hqd.jjvm.util.HexStrTransformUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * 属性信息
 *
 * @author hqd
 */

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AttributeInfo {
    /**
     * 属性名称
     */
    private Integer attributeNameIndex;
    /**
     * 属性长度
     */
    private Integer attributeLength;
    /**
     * 属性字节
     */
    private byte[] byteCodes;

    public AttributeInfo(Integer attributeNameIndex, Integer attributeLength) {
        this.attributeNameIndex = attributeNameIndex;
        this.attributeLength = attributeLength;
    }

    protected <T extends AttributeInfo> AttributeInfo newAttributeInfo(Integer attributeNameIndex, Integer attributeLength, ClassFile classFile) {
        return this;
    }

    /**
     * 创建一个新属性
     *
     * @param classFile
     * @param <T>
     * @return
     */
    public <T extends AttributeInfo> AttributeInfo newAttributeInfo(ClassFile classFile) {
        this.attributeNameIndex = HexStrTransformUtil.parseHexToInt(classFile.readU2());
        this.attributeLength = HexStrTransformUtil.parseHexToInt(classFile.readU4());
        return newAttributeInfo(attributeNameIndex, attributeLength, classFile);
    }
}

再定义一个枚举类型来映射属性类型,代码如下:


import org.apache.commons.lang.StringUtils;

/**
 * 属性类型
 */
public enum AttributeType {
    /**
     * 方法表	Java代码编译成的字节码指令
     */
    Code("Code"),
    /**
     * 字段表 final关键字定义的常量池
     */
    ConstantValue("ConstantValue"),
    /**
     * 类,方法
     * 字段表 被声明为deprecated的方法和字段
     */
    Deprecated("Deprecated"),

    /**
     * 方法表
     * 方法抛出的异常
     */
    Exceptions("Exceptions"),


    /**
     * 类文件
     * 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
     */
    EnclosingMethod("EnclosingMethod"),
    /**
     * 类文件
     * 内部类列表
     */
    InnerClasses("InnerClasses"),
    /**
     * Code属性
     * Java源码的行号与字节码指令的对应关系
     */
    LineNumberTable("LineNumberTable"),
    /**
     * Code属性
     * 方法的局部变量描述
     */
    LocalVariableTable("LocalVariableTable"),
    /**
     * Code属性
     * JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配
     */
    StackMapTable("StackMapTable"),
    /**
     * 类,方法表
     * 字段表 用于支持泛型情况下的方法签名
     */
    Signature("Signature"),

    /**
     * 类文件
     * 记录源文件名称
     */
    SourceFile("SourceFile"),
    /**
     * 类文件
     * 用于存储额外的调试信息
     */
    SourceDebugExtension("SourceDebugExtension"),
    /**
     * 类,方法表
     * 字段表 标志方法或字段为编译器自动生成的
     */
    Synthetic("Synthetic"),
    /**
     * 类
     * 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
     */
    LocalVariableTypeTable("LocalVariableTypeTable"),
    /**
     * 类,方法表
     * 字段表 为动态注解提供支持
     */
    RuntimeVisibleAnnotations("RuntimeVisibleAnnotations"),
    /**
     * 表,方法表
     * 字段表 用于指明哪些注解是运行时不可见的
     */
    RuntimeInvisibleAnnotations("RuntimeInvisibleAnnotations"),
    /**
     * 方法表
     * 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法
     */
    RuntimeVisibleParameterAnnotations("RuntimeVisibleParameterAnnotations"),
    /**
     * 方法表
     * 作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数
     */
    RuntimeInvisibleParameterAnnotations("RuntimeInvisibleParameterAnnotations"),
    /**
     * 方法表
     * 用于记录注解类元素的默认值
     */
    AnnotationDefault("AnnotationDefault"),
    /**
     * 类文件用于保存invokeddynamic指令引用的引导方式限定符
     */
    BootstrapMethods("BootstrapMethods");
    private String code;

    AttributeType(String code) {
        this.code = code;
    }

    public static AttributeType getTypeByVal(String code) {
        if (StringUtils.isNotBlank(code)) {
            AttributeType[] attributeTypes = AttributeType.values();
            for (AttributeType type : attributeTypes) {
                if (type.code.equals(code)) {
                    return type;
                }
            }
        }
        return null;
    }

    public String getCode() {
        return code;
    }
}

由于属性种类繁多,其他属性都解析方式都是类似的。这里就以ConstantValue为例,按照虚拟机规范,ConstantValue结构如下:
在这里插入图片描述
根据虚拟机规范,我们定义对应的实体类,代码如下:

import com.hqd.jjvm.classformat.ClassFile;
import com.hqd.jjvm.util.HexStrTransformUtil;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * 常量表达式
 *
 * @author hqd
 */
@Getter
@NoArgsConstructor
public class ConstantValue extends AttributeInfo {
    /**
     * 常量所在常量池的下标
     */
    private Integer constantValueIndex;

    public ConstantValue(Integer attributeNameIndex, Integer attributeLength, Integer constantValueIndex) {
        super(attributeNameIndex, attributeLength);
        this.constantValueIndex = constantValueIndex;
    }

    @Override
    protected ConstantValue newAttributeInfo(Integer attributeNameIndex, Integer attributeLength, ClassFile classFile) {
        this.constantValueIndex = HexStrTransformUtil.parseHexToInt(classFile.readU2());
        return new ConstantValue(attributeNameIndex, attributeLength, constantValueIndex);
    }
}

和常量池一样,我们创建一个工厂类根据属性类型创建对应的属性实体类,代码如下:


import com.hqd.jjvm.classformat.AttributeType;
import com.hqd.jjvm.classformat.ClassFile;

/**
 * 属性工厂
 *
 * @author hqd
 */
public class AttributeInfoFactory {
    private static final String BASE_PACKAGE = "com.hqd.jjvm.classformat.attributeinfo.";

    private static Class<? extends AttributeInfo> getClassType(AttributeType attributeType) throws ClassNotFoundException {
        Class<? extends AttributeInfo> clazz = null;
        try {
            clazz = (Class<? extends AttributeInfo>) Class.forName((BASE_PACKAGE + attributeType.getCode()).intern());
        } catch (ClassNotFoundException e) {
            clazz = (Class<? extends AttributeInfo>) Class.forName((BASE_PACKAGE + attributeType.getCode() + "Attribute").intern());
        }
        return clazz;
    }

    public static AttributeInfo newAttributeInfo(ClassFile classFile, AttributeType attributeType) {
        try {
            Class<? extends AttributeInfo> clazz = getClassType(attributeType);
            // 获取带参构造方法对象
            // public Constructor<T> getConstructor(Class<?>... parameterTypes)
            AttributeInfo attributeInfo = clazz.newInstance();
            attributeInfo.newAttributeInfo(classFile);
            return attributeInfo;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

由于属性类型比较多,不想一个个判断,这里偷个懒,根据反射去获取对应的类,有了工厂类,后边就简单,依次读取信息就行了。如下:
在这里插入图片描述


总结

   这一章先聊了一下Class搜索和Class文件的解析,相对来说比较简单一些,主要弄明白常量池结构和属性结构即可。这里我们还缺少类加载器来加载Class文件,这个留到后边的章节再说┗(•ω•;)┛

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穷儒公羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值