读取类路径下的class文件
1.思路
加载class文件时,会从 启动类路径(/jre/lib), 扩展类路径(/jre/lib/ext), 用户类路径(用户当前路径), 中加载文件; 因此jvm启动时会将上述三种类型的路径读入内存中,当我们需要使用到某一具体的类文件时,就会从这些路径中加载(加载顺序为 启动类 -》扩展类 -》 用户类); 且加载文件的方式也有多种,如直接从文件夹中加载class文件,从jar中加载class文件,还有就是通配符*加载目录下全部文路劲(包括普通文件夹以及压缩包);
1.1.entry.go
可以从多个地方查找class文件,如jar,文件夹,因此面向接口编程,定义接口
package classpath
import (
"os"
"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)
}
1.2.entry_dir
我们可以从目录下加载class文件
package classpath
import (
"io/ioutil"
"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: 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
}
1.3.entry_zip.go
我们可以从jar包中加载class文件
package classpath
import (
"archive/zip"
"errors"
"io/ioutil"
"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: absPath}
}
func (self *ZipEntry) readClass(className string) ([]byte, Entry, error) {
r, err := zip.OpenReader(self.absPath)
defer r.Close()
if err != nil {
return nil, nil, err
}
for _, f := range r.File {
if f.Name == className {
rc, err := f.Open()
if err != nil {
return nil, nil, err
}
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
}
1.4.entry_composite.go
若我们想要加载的文件夹里面包含很多个jar文件,我们需要将这些路径全部加载一遍并存入数组中
package classpath
import (
"errors"
"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 {
class, from, err := entry.readClass(className)
if err == nil {
return class, from, 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)
}
1.5.wildcard.go
目录下通配符,加载该目录下所有的jar路径以及文件夹路径,同entry_composite.go,目的是将该目录下的类路径全部加载;
package classpath
import (
"os"
"path/filepath"
"strings"
)
func newWildcardEntry(path string) CompositeEntry {
baseDir := path[:len(path)-1]
compositeEntry := []Entry{}
walkFu := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
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, walkFu)
return compositeEntry
}
1.6.classpath.go
加载 启动类路径,扩展类路径,用户类路径入口
package classpath
import (
"os"
"path/filepath"
)
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) 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()
}
func (self *Classpath) parseBootAndExtClasspath(option string) {
jreDir := getJreDir(option)
jreLibPath := filepath.Join(jreDir, "lib", "*")
self.bootClasspath = newWildcardEntry(jreLibPath)
extLibPath := filepath.Join(jreDir, "lib", "ext", "*")
self.extClasspath = newWildcardEntry(extLibPath)
}
func (self *Classpath) parseUserClasspath(option string) {
if option == "" {
option = "."
}
self.userClasspath = newEntry(option)
}
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("Can not find jre folder")
}
func exists(path string) bool {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
1.7.cmd.go
接收用户参数
package main
import (
"flag"
"fmt"
"os"
)
type Cmd struct {
helpFlag bool
versionFlag bool
cpOption string
XjreOption string
class string
args []string
}
func parseCmd() *Cmd {
cmd := &Cmd{}
flag.Usage = printUsage
flag.BoolVar(&cmd.helpFlag, "help", false, "print help message")
flag.BoolVar(&cmd.helpFlag, "?", false, "print help message")
flag.BoolVar(&cmd.versionFlag, "version", false, "print version")
flag.StringVar(&cmd.cpOption, "classpath", "", "classpath")
flag.StringVar(&cmd.cpOption, "cp", "", "classpath")
flag.StringVar(&cmd.XjreOption, "Xjre", "", "path to jre")
flag.Parse()
args := flag.Args()
if len(args) > 0 {
cmd.class = args[0]
cmd.args = args[1:]
}
return cmd
}
func printUsage() {
fmt.Printf("Usage %s [-options] class [args...]\n", os.Args[0])
}
1.8.main.go
主入口
package main
import (
"HandWritingJVM/classpath"
"fmt"
"strings"
)
func main() {
cmd := parseCmd()
if cmd.versionFlag {
fmt.Println("version liyuan - 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: %s class: %s args:%v\n", cmd.cpOption, cmd.class, cmd.args)
className := strings.Replace(cmd.class, ".", "/", -1)
classData, _, err := cp.ReadClass(className)
if err != nil {
fmt.Println("Could not find or load main class: " + cmd.class)
return
}
fmt.Println("class data:\n", classData, string(classData))
}
2.启动过程
在参数中我引用了单独的jar包(),jre目录,以及lib/* 这三种方式;
方式 | 例子 |
---|
entry_zip | E:\m2\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.jar | entry_dir | -Xjre D:\jre1.8 | entry_wildcard | E:\ideaProjects\HandWritingJVM\lib\* |
目录结构如下
2.1.加载启动类路径以及扩展类路径
将启动类路径下的所有jar包路径加载
将扩展类路径下的所有jar包路劲加载
2.2.加载用户类路径
由于用户类路径有多个且以分号划分,因此需要使用entry_composite加载
第一个被加载的jar文件为 mysql-connector-java-8.0.21.jar,使用zip_entry加载;
第二个被加载的项 E:\ideaProjects\HandWritingJVM\lib\* 因此使用通配符加载方式,将该目录下的所有jar文件加载
2.3.找寻 java.lang.Object
依次从 启动类路径,扩展类路劲,用户类路劲下找寻该文件
2.4.运行示例
2.4.1.示例1 java.lang.Object
program arguments为 -Xjre D:\jre1.8 -cp E:\m2\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.jar;E:\ideaProjects\HandWritingJVM\lib* java.lang.Object
2.4.2.示例2 com.mysql.cj.jdbc.Driver
-Xjre D:\jre1.8 -cp E:\m2\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.jar;E:\ideaProjects\HandWritingJVM\lib* com.mysql.cj.jdbc.Driver
2.4.3.示例3 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
-Xjre D:\jre1.8 -cp E:\m2\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.jar;E:\ideaProjects\HandWritingJVM\lib* org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
2.4.4.示例4 com.example.Student(该类不存在)
-Xjre D:\jre1.8 -cp E:\m2\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.jar;E:\ideaProjects\HandWritingJVM\lib* com.example.Student
|