In the previous article, the command-line parameters are parsed through the flag package. In fact, the input parameters are saved in a structure. In the previous article, for example, the command java -classpath hello.jar HelloWorld, how can the HelloWorld class be found? Is it directly found in hello.jar?
Remember java's class loading mechanism? There is a mechanism called parent delegation. For example, why is it useless to define a String class ourselves? Although it can be passed at compile time, an error will be reported at run time. As shown below, why do I prompt that there is no main method in String class? I wrote it clearly! In fact, when the class is loaded, the String class will first be handed over to the startup classloader to load, that is, to find it in the jre/lib directory in the jdk; if not, use the extended classloader to load, that is, to load it in the jre/lib/ext directory in the jdk, and finally under our user's classpath. The default is the current path, or you can specify the user's classpath through the - classpath command;
The String class is obviously in the rt.jar package under the startup class path, so the official String class is loaded, of course, there is no main method!
Go back to the first questions, such as java -classpath hello.jar HelloWorld: where to find HelloWorld? Now it's very clear. Now it's found in jre/lib under jdk. If it can't be found, it's found in jre/lib/ext. If it can't be found, it's found in the path specified by - classpath. Next, it's implemented with go code. The file directory is as follows. This time, the directory is ch02. Based on the previous ch01 implementation, classpath is a Directory, cmd.go and main.go are files
I. add jre path on command line
In order to better specify the path of jre, we add a parameter - Xjre in the command line, such as ch2 -Xjre "D:\java\jdk8" java.lang.String. If the command line does not specify the - Xjre parameter, then go to your computer environment variable to get JAVA_HOME, which is not much to say, so we need to make a change to the structure here in cmd.go, and add a corresponding resolution, not much to say;
2. Define classpath interface
Define the Entry interface in the classpath directory, which is the Entry to find the specified class file. This interface is very important. According to the path actually uploaded after - classpath, you can determine which instance should be obtained to read the class bytecode file in this path. There are four types of structures: CompositeEntry, WildcardEntry, ZipEntry and DirEntry, All of these four structures need to implement the Entry interface. Let's not worry about how they are implemented first. If they have been implemented, we can use them directly;
package classpath import ( "os" "strings" ) //Here is the separator of the classpath, here is the semicolon, because-classpath Multiple directory names can be specified after the command line, separated by semicolons const pathListSeparator = string(os.PathListSeparator) type Entry interface { //This interface is used to find and load class file //className Is the relative path of the class, separated by slashes.class End of file, such as to read java.lang.Object,Should be introduced java/lang/Object.class //The returned data has the class Byte array of files readClass(className string) ([]byte, Entry, error) //Amount to java Medium toString Method String() string } //Create different types of Entry func newEntry(path string) Entry { //If more than one class is passed in as a semicolon, instantiate it CompositeEntry this Entry //for example java -classpath path\to\classes;lib\a.jar;lib\b.jar;lib\c.zip ...This path form if strings.Contains(path, pathListSeparator) { return newCompositeEntry(path) } //Class full path passed in path The string is*End of number //for example java -classpath lib\*... if strings.HasSuffix(path, "*") { return newWildcardEntry(path) } //The full pathname of the class passed in is jar,JAR,zip,ZIP a null-terminated string //for example java -classpath hello.jar ... perhaps java -classpath hello.zip ... if strings.HasSuffix(path, ".jar") || strings.HasSuffix(path, ".JAR") || strings.HasSuffix(path, ".zip") || strings.HasSuffix(path, ".ZIP") { return newZipEntry(path) } //This is what we should do java The file is in the current directory return newDirEntry(path) }
3, Realize the mechanism of parent entrustment
The above is the specific structure defined to analyze different paths. Next, we can implement the parent delegation mechanism. In fact, it is relatively easy. The approximate logic is: for example, the command line input is. \ ch02.exe -Xjre "D:\java\jdk8\jre" java.lang.Object, first of all, we will judge whether the jre directory "D:\java\jdk8\jre" provided by us exists. If it does not exist, we will get the jre of the environment variable. Anyway, we will get the jre path;
Then get the Lib / * and lib/ext / * under jre, instantiate two Entry instances respectively (in fact, each Entry instance is to read the io stream of the files in each different path), which correspond to the startup class path and the extension class path respectively; finally, judge whether the - classpath parameter is provided, if not, it defaults to all the current directory File for this user class path
package classpath import ( "os" "path/filepath" ) //Three kinds of path corresponding Entry type Classpath struct { //Start class path bootClasspath Entry //Extension class path extClasspath Entry //User defined classpath userClasspath Entry } //jreOption This parameter is used to read the startup class and extension class //cpOption This parameter is used to resolve user classes //Command line input .\ch02.exe -Xjre "D:\java\jdk8\jre" java.lang.Object func Parse(jreOption string, cpOption string) *Classpath { cp := &Classpath{} //Parsing start class path and extension class path cp.parseBootAndClasspath(jreOption) cp.parseUserClasspath(cpOption) return cp } //Splice the paths of the startup class and extension class, and instantiate the corresponding Entry func (this *Classpath) parseBootAndClasspath(jreOption string) { //This is the judgment jre If the path exists, it is obtained in the environment variable if it does not exist JAVA_HOMR variable+jre //All in all, I try my best to get it jdk Lower jre Folder full path jreDir := getJreDir(jreOption) //Because we found jdk Lower jre Folder, then the next step is to find the directory where the startup class and extension class are located //Splicing path: jre/lib/* jreLibPath := filepath.Join(jreDir, "lib", "*") this.bootClasspath = newWildcardEntry(jreLibPath) //Splicing path: jre/lib/ext/* jreExtPath := filepath.Join(jreDir, "lib", "ext", "*") this.extClasspath = newWildcardEntry(jreExtPath) } //This function is to get the correct jre Folder, note jreOption It's an absolute path func getJreDir(jreOption string) string { //If the file path passed in exists, return if jreOption != "" && exists(jreOption) { return jreOption } //If the path passed in does not exist, then judge whether there is a path under the current path jre Folder if exists("./jre") { return "./jre" } //The current path does not exist, nor does it exist under the current path jre Folder, then get it directly jdk Lower jre Full path if jh := os.Getenv("JAVA_HOME"); jh != "" { return filepath.Join(jh, "jre") } //If it doesn't exist, it will be thrown out without this folder panic("can not find jre folder ") } //Determine whether a directory exists, and return if it exists true,Return if it doesn't exist false func exists(jreOption string) bool { if _, err := os.Stat(jreOption); err != nil { if os.IsNotExist(err) { return false } } return true } //Load the user class if-classpath If the parameter of is empty, the current path is the path of the user class by default func (this *Classpath) parseUserClasspath(cpOption string) { if cpOption == "" { cpOption = "." } this.userClasspath = newEntry(cpOption) } //This method can see that the parent delegation mechanism is implemented //stay jdk Traverse startup class, extension class and user-defined class in,this ReadClass Is a public method that can be called in other packages func (this *Classpath) ReadClass(className string) ([]byte, Entry, error) { className = className + ".class" if data, entry, err := this.bootClasspath.readClass(className); err == nil { return data, entry, err } if data, entry, err := this.extClasspath.readClass(className); err == nil { return data, entry, err } return this.userClasspath.readClass(className) } func (this *Classpath) String() string { return this.userClasspath.String() }
4, Modify the main.go file
The startJVM function just printed a line of data. Now we can call the Parse method above. According to the jre and class passed in from the command line, we can find the specified class in jre (note that if the jre path specified here does not exist, we will get the jre in the environment variable) according to the parent delegation mechanism, load the class bytecode file of the class into memory, and then give the Print out;
package main import ( "firstGoPrj0114/jvmgo/ch02/classpath" "fmt" "strings" ) //Command line input .\ch02.exe -Xjre "D:\java\jdk8\jre" java.lang.Object func main() { cmd := parseCmd() if cmd.versionFlag { fmt.Println("version 1.0.0") } else if cmd.helpFlag || cmd.class == "" { printUsage() } else { startJVM(cmd) } } //This function is mainly modified func startJVM(cmd *Cmd) { //afferent jdk Medium jre Full path and class name will go inside lib To find or lib/ext Find the corresponding class in //Command line input .\ch02.exe -Xjre "D:\java\jdk8\jre" java.lang.Object cp := classpath.Parse(cmd.XjreOption, cmd.cpOption) fmt.Printf("classpath:%v class:%v args:%v\n", cp, cmd.class, cmd.args) //In the full class name.Turn to/,Read in the form of directory class Files, such as the above java.lang.Object It becomes java/lang/Object className := strings.Replace(cmd.class, ".", "/", -1) //When reading the specified class, there will be a sequence. First, start the required class and try to load it, then load it in the extended class directory, and finally load it in the user-defined directory //There are many ways to define a user-defined directory. You can specify yes.zip mode,It can also be.jar mode classData, _, err := cp.ReadClass(className) if err != nil { fmt.Printf("Could not find or load mian class %s\n", cmd.class) return } fmt.Printf("class data:%v\n", classData) }
5. Implementation class of entry interface
Why is this last? Because I don't think it's the core. I've made clear the basic logic in front of me, and then I'll search and read several files with different paths;
As mentioned earlier, there are many parameters after the - classpath we passed in, such as:
//Corresponding DirEntry java -classpath path\to\service HelloWorld //Corresponding WildcardEntry java -classpath path\to\* HelloWorld //Corresponding ZipEntry java -classpath path\to\lib2.zip HelloWorld
java -classpath path\to\hello.jar HelloWorld //Since there can be multiple paths, the corresponding CompositeEntry java -classpath path\to\classes\*;lib\a.jar;lib\b.jar;lib\c.zip HelloWorld
5.1 DirEntry
This is the easiest. There is only an absolute path in the structure
package classpath import ( "io/ioutil" "path/filepath" ) //A structure is equivalent to a class, newDirEntry Equivalent to the construction method, the following readClass and String Is the way to implement the interface type DirEntry struct { //This is used to store the absolute path absString string } //Return to one DirEntry Example func newDirEntry(path string) *DirEntry { //Convert parameter to absolute path,If it is used on the command line, it will be very accurate to the current file's parent file+Current file //If it is used in the editor, only the current project path will be reached+File path absDir, err := filepath.Abs(path) if err != nil { panic(err) //Terminate program operation } return &DirEntry{absString: absDir} } //DirEntry Realized Entry Of readClass Methods, splicing class The absolute path to the bytecode file, and then use the ioUtil Provided in the package ReadFile Function to read func (self *DirEntry) readClass(className string) ([]byte, Entry, error) { fileName := filepath.Join(self.absString, className) data, err := ioutil.ReadFile(fileName) return data, self, err } //It has also been realized. Entry Of String Method func (self *DirEntry) String() string { return self.absString }
5.2 ZipEntry
This is easier, because there can be multiple files in the zip package, so it's just to traverse and compare the file names
package classpath import ( "archive/zip" "errors" "io/ioutil" "path/filepath" ) //There's also an absolute path type ZipEntry struct { absPath string } //Constructor func newZipEntry(path string) *ZipEntry { abs, err := filepath.Abs(path) if err != nil { panic(err) } return &ZipEntry{absPath: abs} } //from zip Analysis in package class File, key here func (self *ZipEntry) readClass(className string) ([]byte, Entry, error) { //go There is a secondary school zip Packet read zip Files of type reader, err := zip.OpenReader(self.absPath) if err != nil { return nil, nil, err } //This keyword is followed by the current readClass Method will execute after execution defer reader.Close() //ergodic zip Is the file name in the package the same as that provided on the command line for _, f := range reader.File { if f.Name == className { rc, err := f.Open() if err != nil { return nil, nil, err } //defer Key is used to close an open file 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) } //Implementation of interface String Method func (self *ZipEntry) String() string { return self.absPath }
5.3 CompositeEntry
Note that this is the case of multiple paths!
package classpath import ( "errors" "strings" ) //Notice, this is a[]Entry Type Oh, because of this Entry There are multiple paths in the corresponding command line //Multiple paths are separated by semicolons, so we use semicolons to divide them into multiple paths, each of which can be instantiated Entry //Let's instantiate Entry It's all stored in this slice 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 } //Because there are multiple Entry,Let's go through it and call each one Entry Of readClass Method func (self CompositeEntry) readClass(className string) ([]byte, Entry, error) { for _, entry := range self { data, from, err := entry.readClass(className) if err == nil { return data, 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) }
5.4 WildcardEntry
This corresponds to the path with the wildcard *, which is also a CompositeEntry;
package classpath import ( "os" "path/filepath" "strings" ) func newWildcardEntry(path string) CompositeEntry { //Remove the last* baseDir := path[:len(path)-1] // remove * //Actually this kind of Entry Namely CompositeEntry compositeEntry := CompositeEntry{} //For a function, the following is to pass the function as a parameter. This usage is not very familiar, but I feel that it is similar to jdk8 Zhong Chuan Lambda //It's the same as a parameter, right walkFn := func(path string, info os.FileInfo, err error) error { if err != nil { return err } //Skip subdirectories with wildcards*In the subdirectory under jar The package is not searchable if info.IsDir() && path != baseDir { return filepath.SkipDir } //If it is jar Package file, instantiate ZipEntry,Then add to compositeEntry Go inside if strings.HasSuffix(path, ".jar") || strings.HasSuffix(path, ".JAR") { jarEntry := newZipEntry(path) compositeEntry = append(compositeEntry, jarEntry) } return nil } //This function is traversal baseDir All files in filepath.Walk(baseDir, walkFn) return compositeEntry }
Six. Test
In fact, it's almost the same. According to the command entered in the command line, use the parent delegation mechanism to find the specified class in the specified JRE (or JRE of the environment variable). If not, find it in the current directory of the user. After finding the bytecode file, read the file. The final directory is as follows:
For example, if we want to output the bytecode file of the Object class in jdk8, we first need to go install it according to the way we said in the previous article, and there will be a ch02.exe executable file in the bin directory of the workspace, or we can get the same result without specifying the - Xjre parameter;
You can also test the previously compressed jar package. Note: specify the full path of the jar package!
As for the numbers above, this is the bytecode file. The format of each bytecode file is almost the same, which is composed of magic number, minor version number, major version number, thread pool size, thread pool, etc. It's very easy! Next I'll talk about how to parse this bytecode file...