项目地址: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虚拟机》