实现更改Java工作目录的效果

时间:2018-04-26 19:18:39

标签: java relative-path working-directory

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

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()或试图打开文件进行阅读或写作,那么该文件似乎没有&# 39; t存在,因为它实际上试图打开/cwd/foo/bar。实际上,如果fooBar

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

实际上可以工作,因为File对象实际上包含绝对引用。

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

4 个答案:

答案 0 :(得分:1)

更改库的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

当然,这会强制您编写完整的包装器,并使用套接字和序列化或您选择的任何其他通信策略进行通信。从好的方面来说,这将允许您并排启动库的多个实例,而不会使它们的CWD相互干扰 - 代价是JVM和通信开销。

答案 1 :(得分:0)

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

因此,假设您当前的目录为/user/home,并且您想引用/user/tarun/foo/bar,那么您将向库中提供../tarun/foo/bar的相对路径。

为此您可以使用下面SO线程

中讨论的代码

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();

答案 2 :(得分:0)

这里有一个很大的大免责声明!不要这样做!!!!

一旦设置了根目录,你就不应该设置它。我不确定这里的设计理念是什么,但我想它与安全性有关,不允许恶意代码让你的应用程序做出意想不到的事情。也就是说,我们可以通过一些讨厌的反思来解决这个问题。

我通过调试.MP4类找到它的默认目录并从中加载文件来实现此hack。我使用反射来编辑我看到它使用的Paths类的值。这个hack将依赖于操作系统,因为它编辑FileSystem实例。它可能还需要调整,因为不能保证实例不会被更新。您必须确保在所有目标系统上对其进行测试,因为它们将具有不同的FileSystem实现(但请不要执行此操作)

此外,随着Java 9中的新变化,您将无法轻松地使这个hack工作(他们不允许使用反射来编辑未被&#34;模块&#34;)暴露的类。最好的办法是启动一个新的JVM实例,并在传入FileSystem属性的情况下进行调用。这不是一个漂亮的解决方案,但你所使用的代码库也不是。您应该积极尝试以更明智的方式解决这个问题。 (如果业务功能真的很重要,也许重新编写库是最好的方法)

使用我的工作目录中的文件和我的-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);
}

请注意并注意JVM刚刚发出的警告。

答案 3 :(得分:0)

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