二、类加载之class文件的读取

  • Post author:
  • Post category:其他


获取到命令行参数之后,可以开始启动jvm了。启动jvm的第一步就是用类加载器加载main方法所在的类,这样才能开始执行main方法。本章主要说的就是类加载的第一步:class文件的读取,而读到class文件之后如何解析将在下一章介绍。

为了保证java类库的安全性,java类加载器需要遵循双亲委派原则。也就是说有三个主要的类加载器:

1.bootstrap类加载器,加载的是/jre/lib文件夹下的类,是java的核心类库

2.ext类加载器,加载的是/jre/lib/ext文件夹下的类,是扩展类库

3.application类加载器,加载的是classpath下的类,即应用程序的类,main方法下的类库

关于双亲委派模型可以参考:

https://www.cnblogs.com/parent-absent-son/p/9872443.html


使用classpath类来表示这三个类路径,Entry接口用来代表一个类路径下的类集合,可以有不同实现

type Classpath struct {
	bootstrap Entry
	ext Entry
	application Entry
}

Entry接口定义如下,定义了读取class文件的接口,并且给出了统一创建Entry实例的工厂方法

type Entry interface {
	readClass(path string) (clazz []byte,entry Entry,err error)
}

func newEntry(path string) Entry {
	fmt.Println(path)
	if strings.Contains(path, PATH_LIST_SPLITER){
		return newCompositeEntry(path)
	}
	if isZip(path){
		return newZipEntry(path)
	}
	if strings.HasSuffix(path,"*"){
		return newWildcardEntry(path)
	}
	return newDirEntry(path)
}

Entry一共有四种实现:

  1. DirEntry,代表一个文件夹内的类
  2. ZipEntry,代表一个zip包或jar包下的类
  3. CompositeEntry,一个类路径可以由多个路径组成,使用“;”分割多个文件夹路径。也就是说CompositeEntry代表了多个DirEntry或ZipEntry的组合
  4. WildCardEntry,如“/lib/*”,表示lib文件夹下所有ZipEntry的组合,和CompositeEntry类似也是多个Entry的组合

下面看一下这四种Entry分别怎么读取class文件

DirEntry比较简单,只需要判断一下class路径是否在本文件夹下,是的话就使用io读取

func (this DirEntry) readClass(path string) (clazz []byte,entry Entry,err error) {
	if  !strings.Contains(path,this.absPath){
		println(this.absPath+" "+path)
		return nil, nil, nil
	}
	println(this.absPath)
	clazz,err=ioutil.ReadFile(path)
	return clazz, this, err
}

zipEntry需要先解压zip包,然后遍历zip包的路径,寻找是否有符合的路径

func (this ZipEntry) readClass(path string) (clazz []byte,entry Entry,err error){
	reader,err:=zip.OpenReader(this.absPath)
	if err!=nil{
		return nil,nil,err
	}
	defer reader.Close()
	for _,file := range reader.File{//遍历zip中的文件,查找相同文件名的文件
		if file.Name!=path{
			continue
		}
		fileReader,err:=file.Open()//打开文件
		if err!=nil{
			return nil, nil, err
		}
		defer fileReader.Close()
		data,err:=ioutil.ReadAll(fileReader)//读取文件
		if err!=nil{
			return nil, nil, err
		}
		return data,this,nil
	}
	return nil, nil, errors.New("class not found")
}

CompositeEntry是Entry的组合,只需要委托给各个子Entry寻找即可

func (this CompositeEntry) readClass(path string) (clazz []byte,entry Entry,err error){
	for _,entry= range this{
		if clazz,entry,err=entry.readClass(path);clazz!=nil{
			return clazz,entry,err
		}
	}
	//没有entry能读取
	return nil, nil, nil
}

WildCardEntry也是一种CompositeEntry,没有另外定义读取方法

实现了这四种Entry之后,解析三个类加载器的类路径就很简单了,调用统一的工厂方法创建即可

  1. bootstrap加载器路径是“/jre/lib/*”,是一个WildCardEntry
  2. ext加载器路径是“/jre/lib/ext/*”,也是一个WildCardEntry
  3. application加载器路径类似于”.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;”,是一个CompositeEntry

三个Entry创建完成后,就可以按照双亲委派模型来读取class文件了。这里的代码是简化过的,但是也是按照bootstrap,ext,application的顺序来加载类

func (classpath *Classpath) ReadClass(path string) (data []byte,err error) {
	entries:=[]Entry{classpath.bootstrap,classpath.ext,classpath.application}
	for _,entry:= range entries{
		data,_,err=entry.readClass(path)
		if data!=nil{
			return data,err
		}
	}
	return nil, nil
}

github地址:

https://github.com/congye6/jvmgo



版权声明:本文为oBuShanQing原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。