Что хранится в файле манифеста?
В JAR архиве можно найти файл META-INF/MANIFEST.MF . Это манифест архива – хранилище его метаинформации. Манифест обычно добавляется той же утилитой, которой собирается jar-файл: maven-jar-plugin , команда JDK jar .
Манифест – текстовый файл, который состоит из заголовков, строчек вида ключ: значение . Заголовки разделены на секции. Файл начинается с главной секции, описывающей метаинформацию всего архива. Следом, отделенные пустыми строками, идут секции для отдельных пакетов и файлов. В них могут переопределяться общие заголовки. JVM игнорирует неизвестные ей заголовки, что позволяет сторонним утилитам хранить в манифесте свою специфичную метаинформацию.
Вот некоторые из часто используемых заголовков:
• Информация об архиве: Manifest-Version, Created-By, Multi-Release, Built-By
• Main-class – точка входа приложения
• Classpath приложения
• Информация об экстеншне (Specification и Implementation, deprecated)
• Заголовки OSGI бандла
• Типы и хэши файлов архива (особенно применимо в Android приложениях)
Полный список стандартных заголовков можно почитать в документации.
Понимание файла манифеста JAR
Архив Java (JAR) описывается его файлом манифеста. В этой статье рассматриваются его многочисленные возможности, в том числе добавление атрибуции, создание исполняемого файла JAR и встраивание информации о версиях.
Однако давайте начнем с краткого обзора того, что такое файл манифеста.
2. Файл манифеста
Файл манифеста называется MANIFEST.MF и находится в каталоге META-INF в JAR. Это просто список пар ключ-значение, называемый заголовками или атрибутами , сгруппированный в разделы.
Эти заголовки предоставляют метаданные, которые помогают нам описывать аспекты нашего JAR, такие как версии пакетов, какой класс приложения выполнять, путь к классам, материал подписи и многое другое.
3. Добавление файла манифеста
3.1. Манифест по умолчанию
Файл манифеста добавляется автоматически всякий раз, когда мы создаем JAR .
Например, если мы создадим JAR в OpenJDK 11:
jar cf MyJar.jar classes/
Он создает очень простой файл манифеста:
Manifest-Version: 1.0 Created-By: 11.0.3 (AdoptOpenJDK)
3.2. Пользовательский манифест
Или мы можем указать наш собственный файл манифеста.
Например, предположим, что у нас есть пользовательский файл манифеста с именем manifest.txt :
Built-By: foreach
Мы можем включить этот файл, и jar объединит его с файлом манифеста по умолчанию, когда мы используем параметр m :
jar cfm MyJar.jar manifest.txt classes/
Затем результирующий файл манифеста:
Manifest-Version: 1.0 Built-By: foreach Created-By: 11.0.3 (AdoptOpenJDK)
3.3. Мавен
Теперь содержимое файла манифеста по умолчанию меняется в зависимости от того, какие инструменты мы используем.
Например, Maven добавляет несколько дополнительных заголовков:
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven 3.3.9 Built-By: foreach Build-Jdk: 11.0.3
На самом деле мы можем настроить эти заголовки в нашем файле pom.
Скажем, например, что мы хотим указать, кем был создан JAR и пакет:
plugin> groupId>org.apache.maven.pluginsgroupId> artifactId>maven-jar-pluginartifactId> version>3.1.2version> configuration> archive> manifest> packageName>com.foreach.javapackageName> manifest> manifestEntries> Created-By>foreachCreated-By> manifestEntries> archive> configuration> plugin>
Это создает файл манифеста с пользовательским пакетом и заголовками , созданными :
Manifest-Version: 1.0 Build-Jdk-Spec: 11 Package: com.foreach.java Created-By: foreach
4. Заголовки
Заголовок должен соответствовать определенному формату и отделяться новой строкой:
key1: value1 Key2: value2
Действительный заголовок должен иметь пробел между двоеточием и значением . Еще один важный момент — в конце файла должна быть новая строка . В противном случае последний заголовок игнорируется.
Давайте рассмотрим некоторые стандартные заголовки из спецификации и некоторые распространенные настраиваемые заголовки.
4.1. Основные заголовки
Основные заголовки обычно содержат общую информацию:
- Manifest-Version : версия спецификации.
- Created-By : версия инструмента и поставщик, создавший файл манифеста.
- Multi-Release : если true , то это Multi-Release Jar
- Built-By : этот настраиваемый заголовок содержит имя пользователя, создавшего файл манифеста.
4.2. Точка входа и путь к классам
Если наш JAR содержит работающее приложение, мы можем указать точку входа. Точно так же мы можем указать путь к классам . Поступая таким образом, мы избегаем необходимости указывать его, когда хотим его запустить.
- Main-Class : пакет и имя класса с основным методом (без расширения .class)
- Class-Path : разделенный пробелами список относительных путей к библиотекам или ресурсам.
Например, если наша точка входа в приложение находится в Application.class и использует библиотеки и ресурсы, мы можем добавить необходимые заголовки:
Main-Class: com.foreach.Application Class-Path: core.jar lib/ properties/
Путь к классам включает core.jar и все файлы в каталогах lib и properties . Эти активы загружаются относительно места выполнения JAR, а не из самого JAR . Другими словами, они должны существовать вне JAR.
4.3. Версия упаковки и запечатывание
Эти стандартные заголовки описывают пакеты в JAR.
- Название : пакет
- Реализация-Build-Date : дата сборки реализации.
- Реализация-название : название реализации
- Реализация- вендор : поставщик реализации
- Реализация-Версия : версия реализации
- Specification-Title : название спецификации.
- Specification-Vendor : поставщик спецификации
- Версия спецификации: версия спецификации
- Sealed : если true, то все классы для пакета взяты из одного и того же JAR (по умолчанию false)
Например, мы находим эти заголовки манифеста в драйвере MySQL Connector/J JAR . Они описывают версию спецификации JDBC, которой соответствует JAR, а также версию самого драйвера:
Specification-Title: JDBC Specification-Version: 4.2 Specification-Vendor: Oracle Corporation Implementation-Title: MySQL Connector/J Implementation-Version: 8.0.16 Implementation-Vendor: Oracle
4.4. Подписанная банка
Мы можем подписать наш JAR цифровой подписью, чтобы добавить дополнительную безопасность и проверку. Хотя этот процесс выходит за рамки данной статьи, он добавляет в файл манифеста стандартные заголовки, показывающие каждый подписанный класс и его закодированную подпись . Дополнительные сведения см. в документации по подписанию JAR .
4.5. ОСГИ
Обычно также можно увидеть пользовательские заголовки для пакетов OSGI:
- Название пакета : название
- Bundle-SymbolicName : уникальный идентификатор
- Bundle-Version : версия
- Import-Package : пакеты и версии, от которых зависит пакет.
- Export-Package : набор пакетов и версий, доступных для использования.
См. нашу статью «Введение в OSGI », чтобы узнать больше о пакетах OSGI.
5. Разделы
В файле манифеста есть два типа разделов: основной и для каждой записи. Заголовки, которые появляются в основном разделе, применяются ко всему в JAR . Принимая во внимание , что заголовки, которые появляются в разделах для каждой записи, относятся только к именованному пакету или классу .
Кроме того, заголовок, появляющийся в разделе для каждой записи, переопределяет тот же заголовок в основном разделе. Разделы для каждой записи обычно содержат информацию о версиях пакетов и пломбировании, а также цифровую подпись.
Давайте рассмотрим простой пример раздела для каждой записи:
Implementation-Title: foreach-examples Implementation-Version: 1.0.1 Implementation-Vendor: ForEach Sealed: true Name: com/foreach/utils/ Sealed: false
Основной раздел вверху запечатал все пакеты в нашем JAR. Однако пакет com.foreach.utils распечатывается разделом для каждой записи.
6. Заключение
В этой статье представлен обзор того, как добавить файл манифеста в JAR, как использовать разделы и некоторые общие заголовки. Структура файла манифеста позволяет нам предоставлять стандартную информацию, такую как информация о версии.
Однако его гибкость позволяет нам определять любую информацию, которую мы считаем уместной для описания содержимого наших JAR-файлов.
Java: торжественное обращение с jar и атрибутами MANIFEST.MF
Полноценно работать с технологией JAR можно как с помощью утилиты jar, входящей в состав JDK (Java Development Kit), так и с использованием классов JAR API.
Первый способ распространен повсеместно, в то время как второй, зачастую незаслуженно, обделен вниманием и, как показывает практика, напрасно – во многих случаях это может существенно облегчить жизнь разработчика. Именно поэтому в данной статье будут описаны несколько способов применения JAR API для вызова произвольных методов из jar-файла, записи и чтения атрибутов манифеста. (Манифест – (устар.) торжественное письменное обращение верховной власти к народу в связи с важным политическим событием, торжественной датой и т. д. (Толковый словарь русского языка).
Прежде всего следует уделить некоторое внимание утилите jar (http://java.sun.com/javase/6/docs/technotes/tools/solaris/jar.html). Это консольное приложение, запускаемое с набором параметров.
Примеры вызова утилиты:
// Создание нового jar-файла
jar cf file.jar список_файлов
// Просмотр содержимого архива
// Извлечение содержимого из jar-файла
JAR File Specification (http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html), позволяет расположить в архиве file.jar только один манифест с дополнительной служебной информацией META-INF/MANIFEST.MF, представляющий собой текстовый файл в кодировке UTF-8. Если он не был задан при создании jar-файла, используется манифест, который по умолчанию содержит информацию о своей версии и JDK, в которой был создан конкретный jar-файл:
Created-By: 1.6.0 (Sun Microsystems Inc.)
Условно содержимое можно разделить на главную группу (содержит определенные стандартами атрибуты, некоторые из них приведены в таблице 1) и индивидуальную группу (состав которой определяется произвольно).
Таблица 1. Атрибуты главной группы манифеста
Номер версии файла-манифеста. Определяется спецификацией как регулярное выражение следующего вида: цифра+*
Версия и поставщик платформы Java, с помощью которой был создан манифест. Автоматически генерируется утилитой jar
Версия подписи jar-файла. Определяется так же, как и Manifest-Version
Относительные указатели ресурсов (URL) для всех используемых дополнительных классов и библиотек
Относительный путь к главному классу приложения, который должен содержать точку входа – метод main(String[] args). Значение атрибута не должно содержать расширение .class. Атрибут определяется для автономных настольных приложений, которые собраны в выполняемые jar-файлы, которые могут быть запущены виртуальной машиной Java напрямую командой: java -jar file.jar
Имена произвольных атрибутов могут содержать только цифры, строчные и заглавные буквы латинского алфавита и знак «_», а по длине существует ограничение – 70 символов. К значению атрибута никаких требований, кроме элементарной логики, не предъявляется.
Более подробно структуру и состав JAR и META-INF/MANIFEST.MF в рамках этого текста мы затрагивать не будем по той простой причине, что любой желающий может прочесть более подробно cпецификацию JAR.
Использование JAR API
В последней на текущий момент Java 1.6 к JAR API относятся классы пакета java.util.jar, а также классы java.net.JarURLConnection и java.net.URLClassLoader. Ограничимся рассмотрением наиболее используемых классов (см. таблицу 2).
Таблица 2. Наиболее используемые классы в JAR API
Полное название класса
Таблица соответствий между именем атрибута и его значением
Перечисление в виде констант всех имен атрибутов главной группы
Класс описывает элемент jar-файла, например, файл с расширением .class
Наследник класса java.util.zip.ZipFile с поддержкой манифеста. Используется для чтения содержимого jar-файла
Наследник класса java.util.zip.ZipInputStream с поддержкой манифеста. Используется для чтения содержимого jar-файла из любого входящего потока
Наследник класса java.util.zip.ZipOutputStream с поддержкой записи манифеста. Используется для записи содержимого jar-файла в любой исходящий поток
Класс используется для работы с манифестом и его атрибутами
Соединение с jar-файлом или его элементом с помощью указателя ресурсов вида:
ar:!/, например: jar:http://www.site.org/folder/file.jar!/org/site/Clazz.class
Используется для загрузки классов и ресурсов из classpath (может определяться, к примеру, атрибутом манифеста Class-Path)
Запуск методов класса
Прежде всего рассмотрим пример, который будет запускать метод public static void main(String[] args) из произвольного класса в jar-файле. Предположим, что у нас есть некий файл file.jar, который содержит класс pkg.Main:
public class Main
public static void main(String[] args)
MANIFEST.MF архива имеет следующий вид:
Created-By: 1.6.0_03-b05 (Sun Microsystems Inc.)
Вначале необходимо получить URL по имени файла:
URL fileUrl = new File(«file.jar»).toURL();
URL url = new URL(«jar», «», fileUrl + «!/»);
Сам вызов осуществляется следующим образом:
public static final String MAIN_METHOD = «main»;
public void runMainMethod(String className) throws Exception
String[] args = new String[1];
new URLClassLoader(new URL[] < url >);
// Загрузка класса класслоадером
// Получение main-метода и проверка, является ли он точкой входа
mainMethod = clazz.getMethod(MAIN_METHOD, args.getClass());
int mods = mainMethod.getModifiers();
if (mainMethod.getReturnType() != void.class
throw new NoSuchMethodException(MAIN_METHOD);
// Запуск метода с объектом, у которого надо выполнить метод, и параметрами.
// null показывает, что метод статический
Теперь попробуем задействовать JAR API для решения несколько усложненной задачи – теперь необходимо запустить метод main() главного класса jar-файла. Для этого нам надо лишь вызвать уже имеющийся метод runMainMethod() с именем требуемого класса, которое можно получить из манифеста по имени атрибута Main-Class:
private String getMainClassName() throws IOException
JarURLConnection connection = (JarURLConnection) url.openConnection();
Attributes attributes = connection.getMainAttributes();
После вызова метода runMainMethod() в консоли появится надпись:
| main(): запущен |
Генерация манифеста с атрибутами
После того как мы успешно запустили метод main() главного класса jar-файла, попробуем поработать с атрибутами манифеста. Начнем с создания манифеста, а затем попробуем прочитать записанные атрибуты. Итак, класс JarAttributeWriter будет генерировать все атрибуты:
public class JarAttributeWriter
private static final String LINE_TEMPLATE = «%s: %s\n»;
// Метод генерирует текстовый файл – манифест с именем filename
public void generateManifest(String filename)
StringBuffer buf = new StringBuffer();
InputStream inputStream = new ByteArrayInputStream(buf.toString().getBytes(«UTF-8»));
Manifest manifest = new Manifest(inputStream);
OutputStream outputStream = new FileOutputStream(filename);
> catch (IOException e)
// Метод возвращает одну строку манифеста. Метод format() — аналог printf
// в C – осуществляет форматированный вывод
private String getLine(String attributeName, String attributeValue)
return String.format(LINE_TEMPLATE, attributeName, attributeValue);
В результате файл манифеста будет содержать:
Чтение атрибутов из манифеста
Получившийся файл можно использовать в качестве манифеста для jar-архива – это довольно просто, поэтому предположим, что этот манифест уже лежит в jar-файле и требуется считать из него все атрибуты или только некоторые. Эту работу будет выполнять класс JarAttributeReader:
public class JarAttributeReader
private static final String OUTPUT_TEMPLATE = «%s=%s»;
// Метод считывает все атрибуты из jar-файла filename
Attributes getAllAttributes(String filename) throws IOException
jarFile = new JarFile(filename);
// Метод печатает в консоли атрибуты со значениями
void printAllAttributesWithValues(String filename)
Attributes attributes = getAllAttributes(filename);
for (Object o : attributes.keySet())
System.out.println(String.format(OUTPUT_TEMPLATE, o, attributes.getValue(o.toString())));
> catch (IOException e)
В результате в консоли появится запись следующего вида:
Следует отметить, что JAR API предоставляют довольно мощные средства для работы с JAR вообще и MANIFEST.MF, в частности, которые отнюдь не ограничиваются приведенными выше примерами. Ситуаций, когда правильное использования манифеста может существенно облегчить процесс отладки и/или распространения приложений, довольно много. Например, добавление версии приложения в качестве атрибута MANIFEST.MF позволит точно определить номер сборки приложения. Иными словами, в общем случае, в манифест можно записать различные параметры для последующего их использования при запуске приложения.
Manifest
Манифест – это файл с расширением MF, который создаётся всегда, когда формируется Jar файл (за исключением случая, когда в команде на создание Jar файла указана опция M, не спутайте её с опцией m).

Этот создаваемый Jar инструментами манифест назовём манифестом по умолчанию (default manifest). Каждая строка файла манифеста – разделённая двоеточием пара “имя: значение”. Содержание манифеста по умолчанию можно изменить следующим образом. В папке bin нашего проекта создайте текстовый файл MANIFEST.txt, получим:
Введите текст в MANIFEST.txt:
Main-Class: myPack.JarClass

и переведите курсор на новую строку, это важно:
Обратите внимание, на картинке курсор в начале новой строки. Сохраните текстовый файл MANIFEST.txt. В MANIFEST.txt мы объявили главный класс приложения. Наш класс находится в пакете myPack, это отражено в MANIFEST.txt: myPack.JarClass.
Ещё раз. Файл манифеста MANIFEST.MF автоматически создаётся всегда (если нет M в команде). Тогда для чего мы создали ещё какой-то MANIFEST.txt? Для того, чтобы изменить содержание дефолтного манифеста, т.е. MANIFEST.MF. При создании файла Jar строки из нашего MANIFEST.txt добавятся в файл MANIFEST.MF.
Создание Jar файла
Правой кнопкой по значку MyJar.bat -> Изменить и вносим такой текст:
jar cfm JARFile.jar MANIFEST.MF myPack/*.class
здесь m – указание на использование нашего текстового файла, а это MANIFEST.txt, для добавления информации в дефолтный манифест;
JARFile.jar – jar файл, который мы создаём;
MANIFEST.txt – наш текстовый файл, который нужен для изменения дефолтного манифеста MANIFEST.MF;
myPack/*.class – указание архивировать все файлы с расширением class из пакета myPack.

Получаем:

Сохраните MyJar.bat. Теперь можно создать jar файл: два клика по MyJar.bat и через секунду получаем JarFile.jar:

Проверим работу полученного JarFile.jar: два раза кликаем по JarFile.jar и видим знакомое окно:
JarFile.jar можно скопировать и поместить копию в любой каталог – она будет работать.