《自己动手写java虚拟机》学习笔记(三)-----搜索class文件(go)

    项目地址:https://github.com/gongxianshengjiadexiaohuihui

我们都知道,.java文件编译后会形成.class文件,然后class文件会被加载到虚拟机中,被我们使用,那么虚拟机如何从那里寻找这些class文件呢,java虚拟机规范并没有规定虚拟机从哪里寻找,Oracle的java虚拟机实现根据类路径来搜索类按照先后顺序,了路径可分为

  •     启动类路径(bootstrap classpath)
  •     扩展类路径(extension classpath)
  •     用户类路径(user classpath)

    这三种路径呢又有不同的格式 

  1.    直接指定路径,后面跟类名 -cp aaa/bbb/ccc ddd arg1 arg2
  2.    指定类所在的jar文件的路径,后面跟类名: -cp aaa/bbb/ccc.jar ddd arg1 arg2
  3.    指定若干个路径,后面跟类名 -cp aaa1/bbb/ccc;aaa2/bbb/ccc;aaa3/bbb/ccc; ddd arg1 arg2
  4.    指定一个模糊路径,后面跟类名 -cp aaa/bbb/* ddd arg1 arg2   

   所以能我们先实现不同格式的类路径,然后在用它们组合成上面的三种路径,很像组合模式

    新建一个classpath的包

    先定义一个接口表示类路径,然后分别用4种方式去实现这个接口以达到我们的目的

     Entry接口

     

package classpath 

import "os"
import "strings"

const pathListSeparator=string(os.PathListSeparator)

type Entry interface {
	readClass(className string)([]byte,Entry,error)
	String() string
}
func newEntry(path string) Entry{
     //包含分隔符,属于第三种格式的路径
    if strings.Contains(path,pathListSeparator){
    	return newCompositeEntry(path)
    }
    //属于第四种格式的路径
    if strings.HasSuffix(path,"*"){
    	return newWildcardEntry(path)
    }
    //属于第二种格式的路径
    if strings.HasSuffix(path,".jar")||strings.HasSuffix(path,".JAR")||strings.HasSuffix(path,".zip")||strings.HasSuffix(path,".ZIP"){
    	return newZipEntry(path)
    }
    //属于第一种格式的路径
    return newDirEntry(path)
}

   注意一点go结构体不需要显示实现接口,只要方法匹配即可。go没有专门的构造函数,统一用new开头的函数创建结构体实例

DirEntry 第一种格式

package classpath 

import "io/ioutil"
import "path/filepath"

type DirEntry struct{
	absDir string
}

func newDirEntry(path string)*DirEntry{
	absDir,err := filepath.Abs(path)
	if err != nil{
		panic(err)
	}
	return &DirEntry{absDir}
}
func (self *DirEntry)readClass(className string)([]byte,Entry,error){
    //拼接路径
	fileName := filepath.Join(self.absDir,className)
	data,err := ioutil.ReadFile(fileName)
	return data,self,err

}
func (self *DirEntry)String()string{
	return self.absDir
}

ZipEntry 第二种格式的路径

package classpath 

import "archive/zip"
import "errors"
import "io/ioutil"
import "path/filepath"

type ZipEntry struct{
	absPath string
}
func newZipEntry(path string) *ZipEntry{
	absPath,err := filepath.Abs(path)
	if err!= nil{
		panic(err)
	}
	return &ZipEntry{absPath}
}
//遍历该压缩文件里的所有文件
func (self *ZipEntry)readClass(className string)([]byte,Entry,error){
    r,err := zip.OpenReader(self.absPath)
    if err!= nil {
    	return nil,nil,err
    }

    defer r.Close()
    for _,f := range r.File{
    	if f.Name == className{
    		rc,err := f.Open()
    		if err != nil {
    			return nil,nil,err
    		}
            //在return后执行,避免资源未被释放,多个defer时,按定义顺序执行
    		defer rc.Close()
    		data,err := ioutil.ReadAll(rc)
    		if err != nil {
    			return nil,nil,err
    		}
    		return data,self,nil
    	}
    }

    return nil,nil,errors.New("class not found:"+className)
}
func (self *ZipEntry)String() string{
	return self.absPath
}

CompositeEntry 第三种格式路径

CompositeEntry其实是由多个其它格式的Entry组成,所以表示成Entry数组,先把路径根据分隔符拆分成小路径,然后用对应路径的方法遍历即可

package classpath 

import "errors"
import "strings"

type CompositeEntry[]Entry

func newCompositeEntry(pathList string) CompositeEntry{
	compositeEntry :=[]Entry{}
	for _, path := range strings.Split(pathList,pathListSeparator){
		entry := newEntry(path)
		compositeEntry = append(compositeEntry,entry)
	}
	return compositeEntry
}

func (self CompositeEntry) readClass(className string)([]byte,Entry,error){
	for _,entry := range self{
		data,form,err := entry.readClass(className)
		if(err == nil){
			return data,form,nil
		}
	}
	return nil,nil,errors.New("class not found"+className)
}
func (self CompositeEntry) String()string{
	strs := make([]string,len(self))
	for i,entry := range self{
		strs[i] = entry.String()
	}

	return strings.Join(strs,pathListSeparator)
}

WildcardEntry 第四种格式的路径

第四种格式的路径其实和第三种的几乎一样,我们遍历a.*的子文件,找出所有的.jar文件,把路径放入CompositeEntry即可

package classpath 

import "os"
import "path/filepath"
import "strings"

func newWildcardEntry(path string) CompositeEntry{
	baseDir := path[:len(path)-1]
	compositeEntry := []Entry{}
	walkFn := func(path string,info os.FileInfo,err error) error{
		if err != nil {
			return err
		}
		//通配符路径不能递归匹配子目录下的jar文件,所以除了根目录路径,其它目录跳过,不执行,例如a.* 只能是a.b a.c .....
		if info.IsDir() && path != baseDir {
			return filepath.SkipDir
		}
		if strings.HasSuffix(path,".jar") || strings.HasSuffix(path,".JAR") {
			jarEntry := newZipEntry(path)
			compositeEntry = append(compositeEntry,jarEntry)
		}
		return nil 
	}
	filepath.Walk(baseDir,walkFn)
	return compositeEntry
}

接口实现完了,接下来,就是解析穿过来的参数,然后分别从三种路径种寻找class文件即可

classpath.go

package classpath 

import "os"
import "path/filepath"

type Classpath struct{
	//启动类路径 jre/lib/*
	bootClasspath Entry
	//扩展类路径 jre/lib/ext/* 
    extClasspath Entry
    //用户类路径 默认是当前路径
    userClasspath Entry
}
//Parse()函数用-Xjre选项解析启动类路径和扩展类路径,使用-classpath/-cp解析用户类路径
func Parse(jreOption,cpOption string) *Classpath{
	cp := &Classpath{}
	cp.parseBootAndExtClasspath(jreOption)
	cp.parseUserClasspath(cpOption)
	return cp

}
//解析启动类路径和扩展类路径
func (self *Classpath) parseBootAndExtClasspath(jreOption string){
	jreDir := getJreDir(jreOption)
    
    //jre/lib/*
    jreLibPath := filepath.Join(jreDir,"lib","*")
    self.bootClasspath = newWildcardEntry(jreLibPath)
    //jre/lib/ext/*
    jreExtPath := filepath.Join(jreDir,"ext","*")
    self.extClasspath =newWildcardEntry(jreExtPath)

}
	//1优先使用用户输入的-Xjre选项作为jre目录
	//2当前目录下寻找jre目录
	//3用JAVA_HOME环境变量
func getJreDir(jreOption string)string{
	if jreOption != "" && exists(jreOption) {
		return jreOption
	}
	//当前路径下存在jre子目录
	if exists("./jre"){
		return "./jre"
	}
	if jh:=os.Getenv("JAVA_HOME"); jh != "" {
		return filepath.Join(jh,"jre")
	}
	panic("Can not find jre folder!")
}
//用于判断目录是否存在
func exists(path string) bool{
	//返回文件类型的描述FileInfo
   if _,err:=os.Stat(path); err != nil {
   	if os.IsNotExist(err){
   		return false
   	}
   }
   return false
}
//解析用户类路径
func (self *Classpath) parseUserClasspath(cpOption string){
	//默认是当前路径
	if cpOption == ""{
		cpOption = "."
	}
	self.userClasspath = newEntry(cpOption)
}
//依次从启动类路径,扩展类路径,用户路径中搜索class文件
func (self *Classpath) ReadClass(className string)([]byte,Entry,error){
	className = className + ".class"
     //从启动类路径里找
	if data,entry,err := self.bootClasspath.readClass(className); err == nil {
		return data,entry,err
	}
	//从扩展类路径里找
	if data,entry,err := self.extClasspath.readClass(className); err == nil {
		return data,entry,err
	}
    //从用户类路径里找
    return self.userClasspath.readClass(className)

    //这里可能会有疑问,如果都没有呢,用户类路径有默认值,即当前路径
}
func (self *Classpath) String() string{

	return self.userClasspath.String();

}

接下来验证我们写的代码

更改

main.go

调用我们写的搜索类的函数,返回字节码

package main

import "fmt"
import "strings"
import "jvmgo/classpath"

func main(){
	//调用解析命令行的行数,接受解析结果
    cmd:=parseCmd()
    if cmd.versionFlag{
    	fmt.Println("version 0.0.1")
    }else if cmd.helpFlag||cmd.class==""{
    	printUsage()
    }else{
    	startJVM(cmd)
    }
}

func startJVM(cmd *Cmd){
	//解析类路径
	cp := classpath.Parse(cmd.XjreOption,cmd.cpOption)
	fmt.Printf("classpath:%v class:%v args:%v\n",cp,cmd.class,cmd.args)
    //func Replace(s, old, new string, n int) string
    //返回将s中前n个不重叠old子串都替换为new的新字符串,如果n<0会替换所有old子串。
	className := strings.Replace(cmd.class,".","/",-1)
	classData, _, err := cp.ReadClass(className)
	if err != nil {
		fmt.Printf("Cound not find or load main class %s\n",cmd.class)
		return 
	}
	fmt.Printf("class data:%v\n",classData)
}

结果

参考资料:《自己动手写java虚拟机》

猜你喜欢

转载自blog.csdn.net/qq_33543634/article/details/83014569