user.dir 与 jvm 启动路径不一致时引发的一系列问题

** java.io.File**
new File(path)创建一个文件类对象,path可以是绝对路径,也可以是相对路径。

默认情况下,java.io 包中的类总是根据当前用户目录来解析相对路径名。此目录由系统属性 user.dir 指定,通常是 Java 虚拟机的调用目录。

如果修改了user.dir呢?

    @Test
    /**
     * user.dir 属性? ×
     * 结合 test03 可知:
     * user.dir 属性:
     * 影响 File::getAbsoluteFile() 与 File::getAbsolutePath()
     * 但是与File::exists() 无关
     */
    public void test02() throws IOException {
        String relativePath = "target/test-classes/data/file01.txt";

        // debug maven test 时:
        // user.dir 为 E:\Documents\IdeaProjects\MyWorkSpace\prj01\modelA
        // file.exists() 为 false
        // file.getAbsoluteFile.exists() 为 true
        System.out.println("original user.dir is: " + System.getProperty("user.dir"));

        File file = new File(relativePath);
        System.out.println("\tfile exist: " + file.exists() +
                "\n\t\tpath=" + file.getPath() +
                "\n\t\tabsolutePath=" + file.getAbsolutePath() +
                "\n\t\tabsolutrFile exist:" + file.getAbsoluteFile().exists());

        // 修改 user.dir 为 e:/ 后:
        // file.exists() 为 false
        // file.getAbsoluteFile.exists() 为 false
        System.setProperty("user.dir", "e:/");
        System.out.println("after change user.dir is: " + System.getProperty("user.dir"));

        File file1 = new File(relativePath);
        System.out.println("\tfile exist: " + file1.exists() +
                "\n\t\tpath=" + file1.getPath() +
                "\n\t\tabsolutePath=" + file1.getAbsolutePath() +
                "\n\t\tabsolutrFile exist:" + file.getAbsoluteFile().exists());
    }

测试可知,修改了 user.dir 后,file.exists()还是和之前一样,为true;发生变化的只有 file.getAbsoluteFile()。
解释:

  • 如果System Property user.dir被修改,看起来位置变了,但是真正创建(java的io相当于在app和os之前加了一个中间层)文件的时候,os仍然使用了java应用真正启动的当前目录。这个问题在java的bug库中找到了说明,原来system properties中的 user.dir 或者说启动时传的 -Duser.dir 不应该被修改,他们应该是readonly属性(虽然jdk没有做强制的约束,只是口头上的规约)。这是因为jvm启动时候,对于os来说已经记录了一个 CWD(工作路径) ,后面简单地修改它,是不会影响os kernel的,并且也不应该影响(JNI可以,但是jdk官方也不建议这么做)。
  • 每次调用 getAbsolutePath() 的时候,如果File是根据相对路径构造的,jvm会根据 user.dir 变量的值加上相对路径做为绝对路径。

进一步验证:

    /**
     * user.dir 变量 和 File 和 File::getAbsoluteFile 之间有什么关系?
     */
    @Ignore
    @Test
    public void test03() throws IOException {
        File f1 = new File("target/test-classes/data/file01.txt");

        // 为什么修改了 user.dir ,绝对路径变为 e:\\target\test-classes\data\file01.txt target\test-classes\data\file01.txt 后,
        // f1.exists() 还是 true;而 f1.getAbsoluteFile().exists() 为 false?
        // 相对路径 相对于 jvm 运行项目时的根目录?  与 user.dir 的关系是什么?
        System.out.println(f1.getAbsolutePath() + " " + f1.getPath() + " " + (f1.exists() ? "exists" : "does not exist"));
        System.out.println(f1.getAbsolutePath() + " " + (f1.getAbsoluteFile().exists() ? "exists" : "does not exist"));

        System.out.println(f1.getAbsolutePath());
        // 写入值成功
        out(f1, "before\n".getBytes());
        out(f1.getAbsoluteFile(), "absolute before\n".getBytes());
        System.setProperty("user.dir", "d:/");
        System.out.println(f1.getAbsolutePath());
        // 写入值成功
        out(f1, "after\n".getBytes());
        // out(f1.getAbsoluteFile(), "absolute after\n".getBytes()); // java.io.FileNotFoundException: d:\\target\test-classes\data\file01.txt (系统找不到指定的路径。)

    }

    private void out(File f, byte[] b) throws IOException {
        // new FileOutputStream() 允许文件名不存在,但是路径必须存在
        FileOutputStream fos = new FileOutputStream(f, true);
        fos.write(b);
        fos.close();
    }

修改了 user.dir 变量后,仍然可以写入值到 file。而写入到 file.getAbsoluteFile()失败。

PS: 最后还有一个问题,jvm可以主动触发产生另一个jvm进程,即 Runtime.getRuntime().exec(String[] cmdarray, String[] envp, File dir) 这里的dir就是子jvm所使用的CWD,如果null的话,他会尝试用当前jvm的CWD来代替。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容