Как получить путь к работающему файлу JAR? - PullRequest
535 голосов
/ 26 ноября 2008

Мой код выполняется внутри JAR-файла, скажем, foo.jar, и мне нужно знать, в коде, в какой папке находится запущенный foo.jar.

Итак, если foo.jar находится в C:\FOO\, я хочу получить этот путь независимо от того, какой у меня текущий рабочий каталог.

Ответы [ 27 ]

499 голосов
/ 26 ноября 2008
return new File(MyClass.class.getProtectionDomain().getCodeSource().getLocation()
    .toURI()).getPath();

Замените "MyClass" на имя вашего класса.

Очевидно, это будет делать странные вещи, если ваш класс был загружен из не файлового местоположения.

186 голосов
/ 27 июля 2011

Лучшее решение для меня:

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

Это должно решить проблему с пробелами и специальными символами.

143 голосов
/ 04 октября 2012

Чтобы получить File для данного Class, есть два шага:

  1. Преобразование Class в URL
  2. Преобразование URL в File

Важно понимать оба шага, а не объединять их.

Если у вас есть File, вы можете позвонить getParentFile, чтобы получить содержащую папку, если это то, что вам нужно.

Шаг 1: Class до URL

Как обсуждалось в других ответах, есть два основных способа найти URL, относящийся к Class.

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

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

У обоих есть свои плюсы и минусы.

Подход getProtectionDomain дает базовое местоположение класса (например, содержащий JAR-файл). Однако возможно, что политика безопасности среды выполнения Java будет вызывать SecurityException при вызове getProtectionDomain(), поэтому, если ваше приложение должно работать в различных средах, лучше всего протестировать во всех них.

Подход getResource дает полный путь ресурса URL-адреса класса, из которого вам потребуется выполнить дополнительные манипуляции со строками. Это может быть путь file:, но он также может быть jar:file: или даже чем-то более неприятным, например bundleresource://346.fwk2106232034:4/foo/Bar.class, при выполнении в среде OSGi. И наоборот, подход getProtectionDomain правильно дает URL file: даже изнутри OSGi.

Обратите внимание, что оба getResource("") и getResource(".") не прошли мои тесты, когда класс находился в файле JAR; оба вызова вернули ноль. Поэтому я рекомендую вызов №2, показанный выше, поскольку он кажется более безопасным.

Шаг 2: URL до File

В любом случае, если у вас есть URL, следующим шагом будет преобразование в File. Это его собственная проблема; см. сообщение в блоге Kohsuke Kawaguchi об этом для получения полной информации, но вкратце, вы можете использовать new File(url.toURI()), пока URL полностью сформирован.

Наконец, я бы очень не одобрил , используя URLDecoder. Некоторые символы URL, в частности : и /, не являются допустимыми символами в кодировке URL. От URLDecoder Javadoc:

Предполагается, что все символы в закодированной строке являются одним из следующих: от "a" до "z", от "A" до "Z", от "0" до "9" и "-", "_ ", ".", а также "*". Символ "%" разрешен, но интерпретируется как начало специальной экранированной последовательности.

...

Есть два возможных способа, которыми этот декодер может работать с недопустимыми строками. Он может оставить недопустимые символы в одиночку или вызвать исключение IllegalArgumentException. Какой подход использует декодер, остается до реализации.

На практике URLDecoder обычно не выбрасывает IllegalArgumentException, как указано выше. И если ваш путь к файлу имеет пробелы, закодированные как %20, этот подход может работать. Однако, если в вашем пути к файлу есть другие не алфавитные символы, такие как +, у вас будут проблемы с URLDecoder искалечением вашего пути к файлу.

Рабочий код

Чтобы выполнить эти шаги, вы можете использовать методы, подобные следующим:

/**
 * 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 Common :

50 голосов
/ 15 августа 2011

Вы также можете использовать:

CodeSource codeSource = YourMainClass.class.getProtectionDomain().getCodeSource();
File jarFile = new File(codeSource.getLocation().toURI().getPath());
String jarDir = jarFile.getParentFile().getPath();
24 голосов
/ 26 ноября 2008

Используйте ClassLoader.getResource (), чтобы найти URL для вашего текущего класса.

Например:

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. Информацию о формате URL-адреса jar см. В руководстве по JarClassLoader .

17 голосов
/ 28 августа 2013

Я удивлен, увидев, что никто недавно не предложил использовать Path. Далее следует цитата: « Класс Path включает в себя различные методы, которые можно использовать для получения информации о пути, доступа к элементам пути, преобразования пути в другие формы или извлечения частей пути «

Таким образом, хорошая альтернатива - получить объект Path как:

Path path = Paths.get(Test.class.getProtectionDomain().getCodeSource().getLocation().toURI());
14 голосов
/ 20 августа 2013

Единственное решение, которое работает для меня на Linux, Mac и Windows:

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();
}
8 голосов
/ 27 мая 2013

У меня была такая же проблема, и я решил ее так:

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

Надеюсь, я вам помог.

8 голосов
/ 29 марта 2012

Вот обновление до других комментариев, которые мне кажутся неполными из-за особенностей

использование относительной «папки» вне файла .jar (в том же банке место нахождения):

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"));
6 голосов
/ 02 ноября 2015

Для получения пути запуска jar-файла я изучил вышеупомянутые решения и перепробовал все методы, которые существуют между собой. Если этот код выполняется в Eclipse IDE, все они должны иметь возможность найти путь к файлу, включая указанный класс, и открыть или создать указанный файл с найденным путем.

Но это сложно, если запустить файл jar, работающий под управлением, напрямую или через командную строку, он потерпит неудачу, поскольку путь к файлу jar, полученный из вышеуказанных методов, даст внутренний путь в файле jar, то есть всегда дает путь как

rsrc: имя-проекта (возможно, я должен сказать, что это имя пакета файла основного класса - указанный класс)

Я не могу преобразовать путь rsrc: ... во внешний путь, то есть при запуске файла JAR вне Eclipse IDE он не может получить путь к файлу JAR.

Единственный возможный способ получить путь к запуску файла JAR вне Eclipse IDE -

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

эта строка кода может возвращать живой путь (включая имя файла) работающего файла JAR (обратите внимание, что путь возврата не является рабочим каталогом), поскольку документ Java и некоторые люди говорили, что он вернет пути все файлы классов в одном и том же каталоге, но, как показывают мои тесты, если в одном и том же каталоге содержится много jar-файлов, он возвращает только путь запуска jar (о проблеме с несколькими путями, которая действительно произошла в Eclipse).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...