《自己動手寫java虛擬機器》學習筆記(三)-----搜尋class檔案(go)
阿新 • • 發佈:2018-11-23
專案地址:https://github.com/gongxianshengjiadexiaohuihui
我們都知道,.java檔案編譯後會形成.class檔案,然後class檔案會被載入到虛擬機器中,被我們使用,那麼虛擬機器如何從那裡尋找這些class檔案呢,java虛擬機器規範並沒有規定虛擬機器從哪裡尋找,Oracle的java虛擬機器實現根據類路徑來搜尋類按照先後順序,了路徑可分為
- 啟動類路徑(bootstrap classpath)
- 擴充套件類路徑(extension classpath)
- 使用者類路徑(user classpath)
這三種路徑呢又有不同的格式
- 直接指定路徑,後面跟類名 -cp aaa/bbb/ccc ddd arg1 arg2
- 指定類所在的jar檔案的路徑,後面跟類名:
- 指定若干個路徑,後面跟類名 -cp aaa1/bbb/ccc;aaa2/bbb/ccc;aaa3/bbb/ccc; ddd arg1 arg2
- 指定一個模糊路徑,後面跟類名 -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虛擬機器》