IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 1.从0开始的手写java虚拟机 - 读取类路径下的class文件 -> 正文阅读

[Java知识库]1.从0开始的手写java虚拟机 - 读取类路径下的class文件

读取类路径下的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 
//若 -cp或-classpath中包含由分号分割的多个类文件,则将其分割并递归调用本函数
	if strings.Contains(path, pathListSeparator) {
		return newCompositeEntry(path)
	}

//若-cp后的值包含*,则说明要将相应目录下的所有class文件加载
	if strings.HasSuffix(path, "*") {
		return newWildcardEntry(path)
	}

//若-cp后的值包含.jar,则说明是jar文件,通过zip方式加载
	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"
)

//读取目录下的class文件
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"
)

//读取zip如.jar中读取class文件
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

//获取所有类路径对应的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"
)

//目录下通配符
///jre/*.jar 不能匹配 /jre/*/*.jar
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"
	//先从启动类路径下加载class文件
	if data, entry, err := self.bootClasspath.readClass(className); err == nil {
		return data, entry, err
	}
	//从扩展类路径下加载class文件
	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)
	//jre/lib
	jreLibPath := filepath.Join(jreDir, "lib", "*")
	self.bootClasspath = newWildcardEntry(jreLibPath)
	//jre/lib/ext
	extLibPath := filepath.Join(jreDir, "lib", "ext", "*")
	self.extClasspath = newWildcardEntry(extLibPath)
}

//若用户为提供 -classpath, -cp选项,则使用当前目录作为用户类路径
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_zipE:\m2\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.jar
entry_dir-Xjre D:\jre1.8
entry_wildcardE:\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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-19 17:30:07  更:2021-11-19 17:30:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:15:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码