Java虚拟机之自己编写命令行工具(下)

上篇编写的是响应命令行输入的功能,本篇将编写反馈命令行输入的功能,即可以读取指定路径上目标文件的内容,没有指定路径时采用默认路径。

思路与流程

假设注册一个Option实例cp

1
options.addOption("cp", "classpath", true, "classpath");
  1. 当在命令行正确输入后,程序就可以得到一个path字符串,这里会出现几种情况:

    • 字符串内用系统分隔符分开,每个分开的部分属于独立的路径;
    • 字符串用“*”结尾,代表访问该路径下的所有文件;
    • 字符串以“.jar”等结尾;
    • 字符串非null;
  2. 当命令行没有输入代表Option以及该Option参数的字符串时,会采用默认路径继续执行;

命令行一旦输入了path并成功得到了path字符串,那么如果这些字符串代表的路径不exists,程序就退出,不会再去默认路径查找。显式获得了路径就不会再去访问默认路径

上述考虑的是用户路径的情形,实际上程序的运行还需要获取系统路径(包括扩展路径,通过-Xjre参数),同用户路径一样,当显式获得了通过命令行指定的系统路径后就不会再去访问默认路径。

接下来还需要实现读取路径上目标文件的内容(如果存在),和获取路径不同的是,即使路径上没有目标文件,程序也不会退出,它会按照系统路径 -> 扩展路径 -> 用户路径的顺序依次查找目标文件并读取其内容,事实上这也符合双亲委派机制。直到这些路径都没有目标文件才抛出表示“不能读取到.class文件”的异常。

路径抽象

字符串以“*”结尾的路径相对来说是最复杂的,就以其为例简要分析。

先看其构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Wildcard_Entry(String path) {
String baseDir = path.substring(0, path.length() - 1);
File file = new File(baseDir);
if (!file.exists())
throw new RuntimeException("不存在当前路径!");
File[] files = file.listFiles();
if (files == null) {
throw new RuntimeException("没有获得目录下的子文件和子目录!");
}
for (File f : files) {
// 如果当前遍历的目录下的实体不是文件,则跳过单次遍历
if (!f.isFile())
continue;
if (f.getName().endsWith(".jar") || f.getName().endsWith(".JAR")) {
cp.add(new Zip_Entry(baseDir + f.getName()));
}
}
// 当前目录下有可能存在目标文件,且同一目录下不会有同名的多个文件
// 所以添加一个Dir_Entry对象处理
cp.add(new Dir_Entry(baseDir));
}

getabsolutepath和getcanonicalpath的区别见文末参考,前者总是一个后者,只不过前者包含的一些相对路径的部分没有绝对化。

关于 File file = new File():

  • 生成的实例file仅仅抽象的表示一个路径(文件夹)或者文件对象,并不会在文件系统真正创建一个路径或文件;
  • 如果文件系统的确存在真实的路径或文件,那么file.exists()就返回true,反之返回false;

接下来再看起readClass方法:

1
2
3
4
5
6
7
8
9
10
@Override
public byte[] readClass(String className) throws IOException {
for (Entry entry : cp) {
if (entry.readClass(className) != null) {
// 不考虑多个路径返回多个结果
return entry.readClass(className);
}
}
return null;
}

本人注释已然说明了,代码中的判断语句不为null证明查找到了目标文件并成功读取,直接返回不会再继续尝试去其它路径查找。

ClassPath编写

Classpath主要用来抽象出系统路径、扩展对象以及用户对象的实例,并在readClass方法中确定了读取路径中目标文件内容的顺序。

需要注意的地方有:

  • File.pathSeparator和File.separator区别:File.pathSeparator指的是分隔连续多个路径字符串的分隔符,而File.separator才是用来分隔同一个路径字符串中的目录的。顺带提一下在main方法中采用File.separator.charAt(0)是为了获得单个字符而非字符串;

  • Intellij中System.getenv(“JAVA_HOME”)并不会返回.bash_profile中的设置,可以在Edit Configurations中手动添加环境变量:String jh = “/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home”;

小结

编写好的程序可以进行多次测试,当本人将参数设置为-cp . java.lang.Object时,虽然当前目录并没有java.lang.Object.class文件,但是由于在此之前会先在系统变量提供的地址中查找,所以能顺利读取其内容。

参考

What’s the difference between getPath(), getAbsolutePath(), and getCanonicalPath() in Java?:重点 -> A canonical path is always an absolute path.