有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

实现更改Java工作目录效果的相对路径

上下文:由于旧的限制,我的软件依赖于调用一个只能接受相对路径作为输入的库。我需要路径相对于已知目录。库可能会在内部调用,如

java.io.File fooBar = new java.io.File("foo/bar");

我需要这个来给我/nwd/foo/bar,而不是说,/cwd/foo/bar,其中/cwd是运行java的工作目录

出于所有目的,我无法修改此库的内部行为。手动重写实例化这些对象的方法基本上涉及重写整个库

一个诱人的解决方案是在调用库之前System.setProperty("user.dir", "/nwd"),但这实际上并没有给我想要的效果。事实上,如果我调用fooBar.getAbsolutePath(),我将得到所需的/nwd/foo/bar,但是如果我选中fooBar.exists()或试图打开该文件进行读写,则该文件似乎不存在,因为它实际上正在试图打开/cwd/foo/bar。事实上,如果fooBar

java.io.File fooBar = new java.io.File(new java.io.File("foo/bar").getAbsolutePath());

这实际上是可行的,因为File对象实际上包含绝对引用

在这一点上,我很沮丧,我不在乎这是否需要一个黑客解决方案。我只需要更改工作目录的效果


共 (4) 个答案

  1. # 1 楼答案

    为什么不将文件从/nwd/foo/bar复制到/cwd/foo/bar,然后可以原样使用现有库:)

  2. # 2 楼答案

    这里有一个重要的免责声明!别这么做

    一旦设置了根目录,就不能设置它。我不确定这里的设计理念是什么,但我认为这与安全性有关,不允许恶意代码让应用程序执行意外操作。也就是说,我们可以通过一些恶劣的反思来解决这个问题

    我通过调试Paths类找到其默认目录并从中加载文件,实现了这种攻击。我使用反射来编辑我看到它使用的FileSystem类的值。当它编辑FileSystem实例时,这种攻击将依赖于操作系统。它可能还需要调整,因为无法保证实例不会被更新。您必须确保在所有目标系统上测试它,因为它们将有不同的FileSystem实现(但请不要这样做)

    此外,随着Java 9中的新变化,您将无法轻松地进行这种攻击(它们不允许使用反射来编辑“模块”未公开的类)。最好的办法是启动一个新的JVM实例,并在那里使用传入的-Droot属性进行调用。这不是一个很好的解决方案,但您正在使用的代码库也不是。你应该积极尝试以更明智的方式解决这个问题。(如果业务功能真的很重要,那么重新编写库可能是最好的方式)

    使用我的工作目录中的文件和C:\\中的文件进行演示:

    public static void main(final String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Files.readAllLines(new File("foo.txt").toPath()).forEach(System.out::println);
    
        final Class windowsFileSystemClass = Thread.currentThread().getContextClassLoader().loadClass("sun.nio.fs.WindowsFileSystem");
        final Field windowsFileSystemClassField = windowsFileSystemClass.getDeclaredField("defaultDirectory");
        windowsFileSystemClassField.setAccessible(true);
    
        final Class windowsPathClass = Thread.currentThread().getContextClassLoader().loadClass("sun.nio.fs.WindowsPath");
        final Field windowsPathFsField = windowsPathClass.getDeclaredField("fs");
        windowsPathFsField.setAccessible(true);
    
        // Hack that uses a path instance to grab reference to the shared FileSystem instance.
        final Object fileSystem = windowsPathFsField.get(Paths.get(""));
        windowsFileSystemClassField.set(fileSystem, "C:\\");
    
        Files.readAllLines(new File("foo.txt").toPath()).forEach(System.out::println);
    }
    

    这将产生:

    I'm in the working directory
    WARNING: An illegal reflective access operation has occurred
    WARNING: Illegal reflective access by com.Main (file:/C:/Users/calve/IdeaProjects/untitled1/out/production/untitled1/) to field sun.nio.fs.WindowsFileSystem.defaultDirectory
    WARNING: Please consider reporting this to the maintainers of com.Main
    WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
    WARNING: All illegal access operations will be denied in a future release
    I'm in C: now
    

    请注意JVM刚刚发出的警告

  3. # 3 楼答案

    我不认为这是一个大问题,因为相对路径也可以从根开始

    因此,假设您当前的目录是/user/home,并且您希望引用/user/tarun/foo/bar,那么您将给出到您的库的../tarun/foo/bar的相对路径

    为此,您可以使用下面讨论的代码

    How to construct a relative path in Java from two absolute paths (or URLs)?

    String path = "/var/data/stuff/xyz.dat";
    String base = "/var/data";
    String relative = new File(base).toURI().relativize(new File(path).toURI()).getPath();
    
  4. # 4 楼答案

    更改库的CWD的另一种方法是在不同的Java进程中启动它,您可以在启动时指定CWD(参见示例ProcessBuilder文档)。如果我理解正确的话,您当前的流量与

     launch program with CWD 'a'
     use library X that expects CWD to be 'b' // <-- problem here
    

    新的流程将是

     launch program with CWD 'a'
     determine desired CWD for launching library X
     launch wrapper for library X with CWD 'b'
        internally, library X is happy because its CWD is as expected
    

    当然,这将迫使您编写一个完整的包装,并使用sockets&;序列化或您选择的任何其他通信策略。从好的方面来看,这将允许您并排启动库的多个实例,而不会让它们的CWD相互干扰——代价是JVM和通信开销