java虚拟机首先要做的事情就是搜索class文件,这里我们用go来实现搜索class文件的功能。
整体流程
Oracle的Java虚拟机根据类路径来搜索类,
我们可以创建类路径,存储类路径项。
类路径可以分为启动类路径,扩展类路径,用户类路径
类路径项可以分为普通Entry,目录Entry,压缩包或JarEntry,复合Entry
类路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| type ClassPath struct { bootClasspath Entry extClasspath Entry userClasspath Entry }
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)
jreLibPath := filepath.Join(jreDir, "lib", "*") self.bootClasspath = newWildcardEntry(jreLibPath)
jreExtPath := filepath.Join(jreDir, "lib", "ext", "*") self.extClasspath = newWildcardEntry(jreExtPath) }
func getJreDir(jreOption string) string { if jreOption != "" && exists(jreOption) { return jreOption } if exists("./jre") { return "./jre" } if jh := os.Getenv("JAVA_HOME"); jh != "" { return filepath.Join(jh, "jre") } panic("找不到jre目录!") }
func exists(path string) bool { if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { return false } } return true }
func (self *ClassPath) parseUserClasspath(cpOption string) { if cpOption == "" { cpOption = "." } self.userClasspath = newEntry(cpOption) }
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() }
|
在这里,我们实现了类路径的解析,以及读取class文件的功能。
分析类路径解析部分
通过jreOption与cpOption来解析类路径,jreOption是-Xjre选项,cpOption是-classpath/-cp选项。
启动类与拓展类使用jreOption,它们是lib或lib/ext目录下的所有类,
所以首先采用通配符Entry生成,将目录全部分析,然后再向下不断分析。
注意的是,我们应该先对Jre目录进行判断,先看jreOption是否存在,如果不存在,再看当前目录下是否存在jre目录,如果还是不存在,再看JAVA_HOME环境变量是否存在,如果还是不存在,就抛出异常。
cpOption是用户类路径,如果用户没有提供-classpath/-cp选项,则使用当前目录作为用户类路径。
分析读取class文件部分
读取class文件,首先将类名转换为相对路径,然后依次从启动类路径、扩展类路径和用户类路径中搜索class文件。
类路径项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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) }
|
其他Entry实现在TRO148/TroJvm中
测试
1 2 3 4
| cp := classpath.Parse(XjreOption, cpOption)
className := strings.Replace(cmd.class, ".", "/", -1) classData, _, err := cp.ReadClass(className)
|
总结
就这样,我们完成了类路径的解析,以及读取class文件的功能。