获取到命令行参数之后,可以开始启动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一共有四种实现:
- DirEntry,代表一个文件夹内的类
- ZipEntry,代表一个zip包或jar包下的类
- CompositeEntry,一个类路径可以由多个路径组成,使用“;”分割多个文件夹路径。也就是说CompositeEntry代表了多个DirEntry或ZipEntry的组合
- 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之后,解析三个类加载器的类路径就很简单了,调用统一的工厂方法创建即可
- bootstrap加载器路径是“/jre/lib/*”,是一个WildCardEntry
- ext加载器路径是“/jre/lib/ext/*”,也是一个WildCardEntry
- 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