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

实现更改Java工作目录的效果,java,relative-path,working-directory,Java,Relative Path,Working Directory,上下文:由于旧的限制,我的软件依赖于调用一个只能接受相对路径作为输入的库。我需要路径相对于已知目录。库可能会在内部调用,如 java.io.File fooBar = new java.io.File("foo/bar"); 我需要这个来给我/nwd/foo/bar,而不是说,/cwd/foo/bar,其中/cwd是运行java的工作目录 出于所有目的,我无法修改此库的内部行为。手动重写实例化这些对象的方法基本上涉及重写整个库 一个诱人的解决方案是在调用库之前只使用System.setProp

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

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());
这实际上是可行的,因为
文件
对象实际上包含绝对引用


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

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

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

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


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

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

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

此外,随着Java9中的新更改,您将无法轻松地进行此攻击(它们不允许使用反射来编辑“模块”未公开的类)。最好的办法是启动一个新的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);
}
这将产生:

我在工作目录中
警告:发生了非法的反射访问操作
警告:com.Main(文件:/C:/Users/calve/IdeaProjects/untitled1/out/production/untitled1/)对字段sun.nio.fs.WindowsFileSystem.defaultDirectory的非法反射访问
警告:请考虑将此报告给COM。
警告:使用--invalize access=warn以启用对进一步非法访问操作的警告
警告:所有非法访问操作将在未来版本中被拒绝
我在C:现在

请注意JVM刚刚产生的警告。

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

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

当然,这将迫使您编写完整的包装,并使用套接字和序列化或您选择的任何其他通信策略进行通信。另一方面,这将允许您并行启动库的多个实例,如果没有它们的cwd相互干扰——以JVM和通信开销为代价。

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

根据您的限制,您将只有棘手的解决方案。为什么不将库加载的文件移动到启动jvm的基本目录中?@davidxxx,因为程序的单个实例可能需要接触多个这样的目录。程序需要与目录无关。
。/nwd/foo/bar
?请注意,
user.dir
和大多数其他类似属性都是只读的(幸运的是,您不想在设置工作文件夹的应用程序的多个组件之间发生冲突,对吧?)启动程序时,您不能从库的正确工作目录启动程序吗?@Ferrybig No,因为对于应用程序的任何给定会话,都有多个这样的目录。这里的问题是,假设库位于正确的工作目录中,则是库提供了相对路径。我没有办法告诉库“使用它作为基本目录”,这就是我寻求改变工作目录的效果的原因
 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