不要调用 URL.getPath() 方法。如您现在所见,它不返回有效的文件名。它只返回 URL 的路径部分(方案和主机之后的部分),所有百分比转义都完好无损。
将 URL 转换为文件的正确方法是 converting it to a URI,然后是 constructing a File from that URI。
所以,正确的代码应该是这样的:
CodeSource source = Main.class.getProtectionDomain().getCodeSource();
if (source != null) {
try {
File jarFile = new File(source.getLocation().toURI());
execPath = String.valueOf(jarFile.getParentFile());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
注意检查是否为空。 The code source can be null,取决于 ClassLoader 愿意并且能够将多少信息传递给它创建的 ProtectionDomain。这意味着尝试在运行时查找 .jar 文件的位置是不可靠的。
如果您想将本机可执行文件与 .jar 文件捆绑在一起,这里有一些更可靠的选项:
- 在您的 .jar 文件中包含一个脚本(Windows 的批处理文件,其他系统的 shell 脚本),该文件将系统属性传递给程序,其中包含可执行文件的位置。
- 将可执行文件捆绑在 .jar 文件中,并在运行时将它们复制到临时文件并使其可执行。
第一种方法将使用系统来确定目录,因为 是可靠的,并将其传递给程序。例如,在 Windows 中:
set execdir=%~dp0..\..
javaw.exe "-Dexecutable.dir=%here%" -jar MyApplication.jar
在 Linux 中:
execdir=`dirname $0`/..
java "-Dexecutable.dir=$execdir" -jar MyApplication.jar
第二种方法需要创建包含可执行文件的 .jar 文件,然后在要运行它们时将它们复制到 .jar 之外:
Path execDir = Files.createTempDirectory(null);
execPath = execDir.toString();
String[] executables;
String os = System.getProperty("os.name");
if (os.contains("Windows")) {
executables = new String[] {
"windows/foo.exe", "windows/bar.exe", "windows/baz.exe"
};
} else if (os.contains("Mac")) {
executables = new String[] {
"mac/foo", "mac/bar", "mac/baz"
};
} else {
executables = new String[] {
"linux/foo", "linux/bar", "linux/baz"
};
}
for (String executable : executables) {
String baseName = executable.substring(executable.lastIndexOf('/') + 1);
Path newFile = execDir.resolve(baseName);
try (InputStream executable =
Main.class.getResourceAsStream(executable)) {
Files.copy(executable, newFile);
}
if (Files.getFileStore(newFile).supportsFileAttributeView(
PosixFileAttributeView.class)) {
Set<PosixFilePermission> perms =
Files.getPosixFilePermissions(newFile);
perms.add(PosixFilePermission.OWNER_EXECUTE);
Files.setPosixFilePermissions(newFile, perms);
}
}