关于java:如何获取正在运行的JAR文件的路径?

How to get the path of a running JAR file?

我的代码运行在一个jar文件中,比如foo.jar,我需要知道代码中运行的foo.jar在哪个文件夹中。

所以,如果foo.jar在C:\FOO\中,那么不管我当前的工作目录是什么,我都希望得到这个路径。


1
2
return new File(MyClass.class.getProtectionDomain().getCodeSource().getLocation()
    .toURI()).getPath();

将"myClass"替换为类的名称。

显然,如果类是从非文件位置加载的,那么这将做一些奇怪的事情。


最佳解决方案:

1
2
String path = Test.class.getProtectionDomain().getCodeSource().getLocation().getPath();
String decodedPath = URLDecoder.decode(path,"UTF-8");

这应该解决带有空格和特殊字符的问题。


要获得给定ClassFile,有两个步骤:

  • Class转换为URL
  • URL转换为File
  • 理解这两个步骤很重要,不要把它们混为一谈。

    一旦你有了File,如果你需要的话,你可以调用getParentFile来获取包含文件夹。

    步骤1:ClassURL

    如其他答案中所讨论的,找到与Class相关的URL有两种主要方法。

  • URL url = Bar.class.getProtectionDomain().getCodeSource().getLocation();

  • URL url = Bar.class.getResource(Bar.class.getSimpleName() +".class");

  • 两者都有利弊。

    getProtectionDomain方法生成类的基本位置(例如,包含jar文件)。然而,Java运行时的安全策略可能会在调用EDCOX1(16)时抛出EDCOX1×15,因此,如果应用程序需要在各种环境中运行,最好在所有这些环境中进行测试。

    getResource方法生成类的完整URL资源路径,您需要从中执行额外的字符串操作。它可能是一条file:路径,但也可能是jar:file:,甚至是在OSGi框架内执行时更糟糕的东西,如bundleresource://346.fwk2106232034:4/foo/Bar.class。相反,即使在OSGi中,getProtectionDomain方法也能正确生成file:URL。

    注意,当类位于JAR文件中时,getResource("")getResource(".")在我的测试中都失败了;两个调用都返回了空值。因此,我建议使用上面显示的2调用,因为它看起来更安全。

    步骤2:URLFile

    不管怎样,一旦你有了一个URL,下一步就是转换为File。这是它自己的挑战;有关详细信息,请参阅Kohsuke Kawaguchi的博客文章,但简而言之,只要URL格式完全正确,就可以使用new File(url.toURI())

    最后,我强烈反对使用URLDecoder。URL的某些字符,尤其是:/不是有效的URL编码字符。来自Urldecoder JavaDoc:

    It is assumed that all characters in the encoded string are one of the following:"a" through"z","A" through"Z","0" through"9", and"-","_",".", and"*". The character"%" is allowed but is interpreted as the start of a special escaped sequence.

    ...

    There are two possible ways in which this decoder could deal with illegal strings. It could either leave illegal characters alone or it could throw an IllegalArgumentException. Which approach the decoder takes is left to the implementation.

    实际上,URLDecoder一般不会像上述威胁那样抛出IllegalArgumentException。如果您的文件路径中有编码为%20的空格,这种方法可能会起作用。但是,如果您的文件路径具有其他非字母数字字符,如+,则会遇到URLDecoder管理文件路径的问题。

    工作代码

    要实现这些步骤,您可能有如下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    /**
     * Gets the base location of the given class.
     * <p>

     * If the class is directly on the file system (e.g.,
     *"/path/to/my/package/MyClass.class") then it will return the base directory
     * (e.g.,"file:/path/to").
     *
    </p>
     * <p>

     * If the class is within a JAR file (e.g.,
     *"/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
     * path to the JAR (e.g.,"file:/path/to/my-jar.jar").
     *
    </p>
     *
     * @param c The class whose location is desired.
     * @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
     */

    public static URL getLocation(final Class<?> c) {
        if (c == null) return null; // could not load the class

        // try the easy way first
        try {
            final URL codeSourceLocation =
                c.getProtectionDomain().getCodeSource().getLocation();
            if (codeSourceLocation != null) return codeSourceLocation;
        }
        catch (final SecurityException e) {
            // NB: Cannot access protection domain.
        }
        catch (final NullPointerException e) {
            // NB: Protection domain or code source is null.
        }

        // NB: The easy way failed, so we try the hard way. We ask for the class
        // itself as a resource, then strip the class's path from the URL string,
        // leaving the base path.

        // get the class's raw resource path
        final URL classResource = c.getResource(c.getSimpleName() +".class");
        if (classResource == null) return null; // cannot find class resource

        final String url = classResource.toString();
        final String suffix = c.getCanonicalName().replace('.', '/') +".class";
        if (!url.endsWith(suffix)) return null; // weird URL

        // strip the class's path from the URL string
        final String base = url.substring(0, url.length() - suffix.length());

        String path = base;

        // remove the"jar:" prefix and"!/" suffix, if present
        if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2);

        try {
            return new URL(path);
        }
        catch (final MalformedURLException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Converts the given {@link URL} to its corresponding {@link File}.
     * <p>

     * This method is similar to calling {@code new File(url.toURI())} except that
     * it also handles"jar:file:" URLs, returning the path to the JAR file.
     *
    </p>
     *
     * @param url The URL to convert.
     * @return A file path suitable for use with e.g. {@link FileInputStream}
     * @throws IllegalArgumentException if the URL does not correspond to a file.
     */

    public static File urlToFile(final URL url) {
        return url == null ? null : urlToFile(url.toString());
    }

    /**
     * Converts the given URL string to its corresponding {@link File}.
     *
     * @param url The URL to convert.
     * @return A file path suitable for use with e.g. {@link FileInputStream}
     * @throws IllegalArgumentException if the URL does not correspond to a file.
     */

    public static File urlToFile(final String url) {
        String path = url;
        if (path.startsWith("jar:")) {
            // remove"jar:" prefix and"!/" suffix
            final int index = path.indexOf("!/");
            path = path.substring(4, index);
        }
        try {
            if (PlatformUtils.isWindows() && path.matches("file:[A-Za-z]:.*")) {
                path ="file:/" + path.substring(5);
            }
            return new File(new URL(path).toURI());
        }
        catch (final MalformedURLException e) {
            // NB: URL is not completely well-formed.
        }
        catch (final URISyntaxException e) {
            // NB: URL is not completely well-formed.
        }
        if (path.startsWith("file:")) {
            // pass through the URL as-is, minus"file:" prefix
            path = path.substring(5);
            return new File(path);
        }
        throw new IllegalArgumentException("Invalid URL:" + url);
    }

    您可以在scijava公共库中找到这些方法:

    • org.scijava.util.classutils
    • org.scijava.util.fileutils.


    您还可以使用:

    1
    2
    3
    CodeSource codeSource = YourMainClass.class.getProtectionDomain().getCodeSource();
    File jarFile = new File(codeSource.getLocation().toURI().getPath());
    String jarDir = jarFile.getParentFile().getPath();


    使用classloader.getresource()查找当前类的URL。

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package foo;

    public class Test
    {
        public static void main(String[] args)
        {
            ClassLoader loader = Test.class.getClassLoader();
            System.out.println(loader.getResource("foo/Test.class"));
        }
    }

    (这个例子取自类似的问题。)

    要找到目录,您需要手动拆分URL。有关jar url的格式,请参阅jarclassloader教程。


    我很惊讶地看到最近没有人提议使用Path。引用如下:"Path类包括各种方法,可用于获取有关路径的信息、访问路径元素、将路径转换为其他形式或提取路径的部分。"

    因此,一个好的选择是让Pathobjest如下:

    1
    Path path = Paths.get(Test.class.getProtectionDomain().getCodeSource().getLocation().toURI());


    唯一适用于Linux、Mac和Windows的解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static String getJarContainingFolder(Class aclass) throws Exception {
      CodeSource codeSource = aclass.getProtectionDomain().getCodeSource();

      File jarFile;

      if (codeSource.getLocation() != null) {
        jarFile = new File(codeSource.getLocation().toURI());
      }
      else {
        String path = aclass.getResource(aclass.getSimpleName() +".class").getPath();
        String jarFilePath = path.substring(path.indexOf(":") + 1, path.indexOf("!"));
        jarFilePath = URLDecoder.decode(jarFilePath,"UTF-8");
        jarFile = new File(jarFilePath);
      }
      return jarFile.getParentFile().getAbsolutePath();
    }


    以下是对其他评论的升级,在我看来,这些评论对于

    using a relative"folder" outside .jar file (in the jar's same
    location):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    String path =
      YourMainClassName.class.getProtectionDomain().
      getCodeSource().getLocation().getPath();

    path =
      URLDecoder.decode(
        path,
       "UTF-8");

    BufferedImage img =
      ImageIO.read(
        new File((
            new File(path).getParentFile().getPath()) +  
            File.separator +
           "folder" +
            File.separator +
           "yourfile.jpg"));


    我也有同样的问题,我用这种方式解决了它:

    1
    2
    3
    File currentJavaJarFile = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().getPath());  
    String currentJavaJarFilePath = currentJavaJarFile.getAbsolutePath();
    String currentRootDirectoryPath = currentJavaJarFilePath.replace(currentJavaJarFile.getName(),"");

    希望我能帮助你。


    为了得到运行jar文件的路径,我研究了上述解决方案,并尝试了所有相互之间存在差异的方法。如果这些代码在Eclipse IDE中运行,那么它们都应该能够找到文件的路径,包括指定的类,并使用找到的路径打开或创建指定的文件。

    但很棘手的是,当直接或通过命令行运行可运行的jar文件时,它将失败,因为从上述方法获得的jar文件路径将在jar文件中给出一个内部路径,也就是说,它总是给出一个路径作为

    rsrc:项目名(也许我应该说它是主类文件的包名——指定的类)

    我不能转换rsrc:…指向外部路径的路径,即当在Eclipse IDE外运行JAR文件时,它无法获取JAR文件的路径。

    获取在Eclipse IDE外运行JAR文件的路径的唯一可能方法是

    1
    System.getProperty("java.class.path")

    这个代码行可以返回正在运行的JAR文件的生存路径(包括文件名)(注意返回路径不是工作目录),因为Java文档和一些人说它将返回同一目录中所有类文件的路径,但是当我的测试在同一目录中包含许多JAR文件时,它只返回P。运行jar的路径(关于多路径问题,实际上它发生在Eclipse中)。


    如果您通过从GNOME桌面环境(而不是从任何脚本或终端)单击JAR来运行JAR,则上面选择的答案将不起作用。

    相反,我喜欢以下解决方案无处不在:

    1
    2
    3
    4
    5
        try {
            return URLDecoder.decode(ClassLoader.getSystemClassLoader().getResource(".").getPath(),"UTF-8");
        } catch (UnsupportedEncodingException e) {
            return"";
        }


    最简单的解决方案是在运行jar时将路径作为参数传递。

    您可以使用shell脚本(在Windows中为.bat,在其他任何地方为.sh)自动执行此操作:

    1
    java -jar my-jar.jar .

    我用.传递当前的工作目录。

    更新

    您可能希望将JAR文件粘贴到子目录中,这样用户就不会意外地单击它。您的代码还应该检查以确保已经提供了命令行参数,并在缺少参数时提供良好的错误消息。


    实际上,这里有一个更好的版本——如果文件夹名中有空间,则旧版本会失败。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
      private String getJarFolder() {
        // get name and path
        String name = getClass().getName().replace('.', '/');
        name = getClass().getResource("/" + name +".class").toString();
        // remove junk
        name = name.substring(0, name.indexOf(".jar"));
        name = name.substring(name.lastIndexOf(':')-1, name.lastIndexOf('/')+1).replace('%', ' ');
        // remove escape characters
        String s ="";
        for (int k=0; k<name.length(); k++) {
          s += name.charAt(k);
          if (name.charAt(k) == ' ') k += 2;
        }
        // replace '/' with system separator char
        return s.replace('/', File.separatorChar);
      }

    至于小程序的失败,您通常无法访问本地文件。我对JWS不太了解,但要处理本地文件,可能无法下载该应用程序。


    在我最终找到一个有效的(短期的)解决方案之前,我不得不到处乱转。可能jarLocation带有前缀,如file:\jar:file\,可以使用String#substring()删除。

    1
    2
    URL jarLocationUrl = MyClass.class.getProtectionDomain().getCodeSource().getLocation();
    String jarLocation = new File(jarLocationUrl.toString()).getParent();


    其他答案似乎指向代码源,即JAR文件位置,而不是目录。

    使用

    1
    return new File(MyClass.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile();


    1
    String path = getClass().getResource("").getPath();

    路径总是引用JAR文件中的资源。


    我试着用

    1
    String folder = MyClassName.class.getProtectionDomain().getCodeSource().getLocation().getPath();

    C:APP> Java-jar应用程序

    运行名为"application.jar"的jar应用程序,在Windows的文件夹"c:app"中,字符串变量"folder"的值为"c:appapplication.jar",我在测试路径的正确性时遇到了问题。

    1
    2
    File test = new File(folder);
    if(file.isDirectory() && file.canRead()) { //always false }

    所以我试图将"测试"定义为:

    1
    2
    String fold= new File(folder).getParentFile().getPath()
    File test = new File(fold);

    要获得正确格式的路径,如"c:app",而不是"c:appapplication.jar",我注意到它可以工作。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static String dir() throws URISyntaxException
    {
        URI path=Main.class.getProtectionDomain().getCodeSource().getLocation().toURI();
        String name= Main.class.getPackage().getName()+".jar";
        String path2 = path.getRawPath();
        path2=path2.substring(1);

        if (path2.contains(".jar"))
        {
            path2=path2.replace(name,"");
        }
        return path2;}

    适用于Windows


    令人沮丧的是,当您在eclipse MyClass.class.getProtectionDomain().getCodeSource().getLocation()中开发时,返回/bin目录,这很好,但是当您将其编译到JAR时,路径中包含了/myjarname.jar部分,这部分为您提供了非法的文件名。

    为了让代码在IDE中工作,并且一旦编译到JAR中,我使用以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    URL applicationRootPathURL = getClass().getProtectionDomain().getCodeSource().getLocation();
    File applicationRootPath = new File(applicationRootPathURL.getPath());
    File myFile;
    if(applicationRootPath.isDirectory()){
        myFile = new File(applicationRootPath,"filename");
    }
    else{
        myFile = new File(applicationRootPath.getParentFile(),"filename");
    }


    已经尝试了上面的几个解决方案,但没有一个能产生正确的结果(可能是特殊的),即运行jar已经在Eclipse中通过"打包外部库"导出。出于某种原因,在这种情况下,所有基于ProtectionDomain的解决方案都会导致空值。

    通过结合上面的一些解决方案,我成功地实现了以下工作代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    String surroundingJar = null;

    // gets the path to the jar file if it exists; or the"bin" directory if calling from Eclipse
    String jarDir = new File(ClassLoader.getSystemClassLoader().getResource(".").getPath()).getAbsolutePath();

    // gets the"bin" directory if calling from eclipse or the name of the .jar file alone (without its path)
    String jarFileFromSys = System.getProperty("java.class.path").split(";")[0];

    // If both are equal that means it is running from an IDE like Eclipse
    if (jarFileFromSys.equals(jarDir))
    {
        System.out.println("RUNNING FROM IDE!");
        // The path to the jar is the"bin" directory in that case because there is no actual .jar file.
        surroundingJar = jarDir;
    }
    else
    {
        // Combining the path and the name of the .jar file to achieve the final result
        surroundingJar = jarDir + jarFileFromSys.substring(1);
    }

    System.out.println("JAR File:" + surroundingJar);

    对其他人不太确定,但在我的例子中,它不适用于"可运行的jar",我通过修复phchen2-answer和这个链接中的另一个代码来实现它:如何获取运行的jar文件的路径?代码:

    1
    2
    3
    4
    5
    6
    7
                   String path=new java.io.File(Server.class.getProtectionDomain()
                    .getCodeSource()
                    .getLocation()
                    .getPath())
              .getAbsolutePath();
           path=path.substring(0, path.lastIndexOf("."));
           path=path+System.getProperty("java.class.path");

    提到它只在Windows中检查,但我认为它在其他操作系统(Linux,MacOs,Solaris)上工作得很好。

    我在同一个目录中有2个.jar文件。我想从一个.jar文件开始另一个.jar文件,它在同一个目录中。

    问题是,当您从cmd启动它时,当前目录是system32

    Warnings!

    • 在我做过的所有测试中,下面的内容似乎都很有效文件夹名为;][[;'57f2g34g87-8+9-09!2#@!$%^^&()()%&$%^@#的它运作良好。
    • 我使用的ProcessBuilder如下:

    ?……

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //The class from which i called this was the class `Main`
    String path = getBasePathForClass(Main.class);
    String applicationPath=  new File(path +"application.jar").getAbsolutePath();


    System.out.println("Directory Path is :"+applicationPath);

    //Your know try catch here
    //Mention that sometimes it doesn't work for example with folder `;][[;'57f2g34g87-8+9-09!2#@!$%^^&()`
    ProcessBuilder builder = new ProcessBuilder("java","-jar", applicationPath);
    builder.redirectErrorStream(true);
    Process process = builder.start();

    //...code

    ??getBasePathForClass(Class classs)号:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
        /**
         * Returns the absolute path of the current directory in which the given
         * class
         * file is.
         *
         * @param classs
         * @return The absolute path of the current directory in which the class
         *         file is.
         * @author GOXR3PLUS[StackOverFlow user] + bachden [StackOverFlow user]
         */

        public static final String getBasePathForClass(Class<?> classs) {

            // Local variables
            File file;
            String basePath ="";
            boolean failed = false;

            // Let's give a first try
            try {
                file = new File(classs.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());

                if (file.isFile() || file.getPath().endsWith(".jar") || file.getPath().endsWith(".zip")) {
                    basePath = file.getParent();
                } else {
                    basePath = file.getPath();
                }
            } catch (URISyntaxException ex) {
                failed = true;
                Logger.getLogger(classs.getName()).log(Level.WARNING,
                       "Cannot firgue out base path for class with way (1):", ex);
            }

            // The above failed?
            if (failed) {
                try {
                    file = new File(classs.getClassLoader().getResource("").toURI().getPath());
                    basePath = file.getAbsolutePath();

                    // the below is for testing purposes...
                    // starts with File.separator?
                    // String l = local.replaceFirst("[" + File.separator +
                    //"/\\\\]","")
                } catch (URISyntaxException ex) {
                    Logger.getLogger(classs.getName()).log(Level.WARNING,
                           "Cannot firgue out base path for class with way (2):", ex);
                }
            }

            // fix to run inside eclipse
            if (basePath.endsWith(File.separator +"lib") || basePath.endsWith(File.separator +"bin")
                    || basePath.endsWith("bin" + File.separator) || basePath.endsWith("lib" + File.separator)) {
                basePath = basePath.substring(0, basePath.length() - 4);
            }
            // fix to run inside netbeans
            if (basePath.endsWith(File.separator +"build" + File.separator +"classes")) {
                basePath = basePath.substring(0, basePath.length() - 14);
            }
            // end fix
            if (!basePath.endsWith(File.separator)) {
                basePath = basePath + File.separator;
            }
            return basePath;
        }

    此方法从存档中的代码调用,返回.jar文件所在的文件夹。它应该在Windows或Unix中工作。

    1
    2
    3
    4
    5
    6
    7
    8
      private String getJarFolder() {
        String name = this.getClass().getName().replace('.', '/');
        String s = this.getClass().getResource("/" + name +".class").toString();
        s = s.replace('/', File.separatorChar);
        s = s.substring(0, s.indexOf(".jar")+4);
        s = s.substring(s.lastIndexOf(':')-1);
        return s.substring(0, s.lastIndexOf(File.separatorChar)+1);
      }

    从位于的代码派生:确定是否从JAR运行


    此代码适用于我:

    1
    2
    3
    4
    5
    6
    private static String getJarPath() throws IOException, URISyntaxException {
        File f = new File(LicensingApp.class.getProtectionDomain().().getLocation().toURI());
        String jarPath = f.getCanonicalPath().toString();
        String jarDir = jarPath.substring( 0, jarPath.lastIndexOf( File.separator ));
        return jarDir;
      }


    我用Java 7编写,在Oracle的运行时在Windows 7中测试,而Ubuntu在开源运行时进行测试。这非常适合这些系统:

    任何运行的JAR文件的父目录的路径(假定调用此代码的类是JAR存档本身的直接子目录):

    1
    2
    3
    4
    5
    6
    try {
        fooDir = new File(this.getClass().getClassLoader().getResource("").toURI());
    } catch (URISyntaxException e) {
        //may be sloppy, but don't really need anything here
    }
    fooDirPath = fooDir.toString(); // converts abstract (absolute) path to a String

    所以foo.jar的路径是:

    1
    fooPath = fooDirPath + File.separator +"foo.jar";

    同样,这并没有在任何Mac或旧的Windows上测试过。


    EDCOX1×3的方法有时可能不起作用,例如当您必须为一些核心Java类找到JAR(例如,在我的例子中,在IBM JDK中的EDCOX1,4类),但是无缝地遵循以下工作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args) {
        System.out.println(findSource(MyClass.class));
        // OR
        System.out.println(findSource(String.class));
    }

    public static String findSource(Class<?> clazz) {
        String resourceToSearch = '/' + clazz.getName().replace(".","/") +".class";
        java.net.URL location = clazz.getResource(resourceToSearch);
        String sourcePath = location.getPath();
        // Optional, Remove junk
        return sourcePath.replace("file:","").replace("!" + resourceToSearch,"");
    }


    我有另一种方法来获取类的字符串位置。

    1
    2
    3
    URL path = Thread.currentThread().getContextClassLoader().getResource("");
    Path p = Paths.get(path.toURI());
    String location = p.toString();

    输出字符串的形式为

    1
    2
    C:\Users\Administrator
    ew Workspace\...

    空格和其他字符被处理,并以不带file:/的形式出现。所以更容易使用。