Чтение моего собственного манифеста Jar
Мне нужно прочитать файл Manifest
, который доставил мой класс, но когда я использую:
getClass().getClassLoader().getResources(...)
Я получаю Manifest
от первого .jar
, загруженного в Java Runtime.
Мое приложение будет запущено из апплета или веб-сайта,
поэтому я не буду иметь доступ к моему собственному файлу .jar
, я думаю.
Я действительно хочу прочитать атрибут Export-package
из .jar
, который был запущен
Felix OSGi, поэтому я могу разоблачить эти пакеты в Felix. Любые идеи?
Ответы
Ответ 1
Вы можете сделать одну из двух вещей:
-
Вызвать getResources()
и перебрать возвращенную коллекцию URL-адресов, прочитав их как манифесты, пока не найдете их:
Enumeration<URL> resources = getClass().getClassLoader()
.getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
try {
Manifest manifest = new Manifest(resources.nextElement().openStream());
// check that this is your manifest and do what you need or get the next one
...
} catch (IOException E) {
// handle
}
}
-
Вы можете проверить, является ли getClass().getClassLoader()
экземпляром java.net.URLClassLoader
. Большинство загрузчиков классов Sun, включая AppletClassLoader
.
Затем вы можете бросить его и называть findResource()
, который был известен - для апплетов - по крайней мере - для возврата необходимого манифеста непосредственно:
URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
try {
URL url = cl.findResource("META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(url.openStream());
// do stuff with it
...
} catch (IOException E) {
// handle
}
Ответ 2
Сначала вы можете найти URL для своего класса. Если это JAR, тогда вы загружаете манифест оттуда. Например,
Class clazz = MyClass.class;
String className = clazz.getSimpleName() + ".class";
String classPath = clazz.getResource(className).toString();
if (!classPath.startsWith("jar")) {
// Class not from JAR
return;
}
String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) +
"/META-INF/MANIFEST.MF";
Manifest manifest = new Manifest(new URL(manifestPath).openStream());
Attributes attr = manifest.getMainAttributes();
String value = attr.getValue("Manifest-Version");
Ответ 3
Вы можете использовать Manifests
из jcabi-manifestests и прочитать любой атрибут из любого доступного MANIFEST.MF с одной строкой:
String value = Manifests.read("My-Attribute");
Единственная зависимость, в которой вы нуждаетесь:
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-manifests</artifactId>
<version>0.7.5</version>
</dependency>
Кроме того, см. это сообщение в блоге для более подробной информации: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html
Ответ 4
Я считаю, что наиболее подходящим способом получить манифест для любого пакета (включая пакет, который загружал данный класс), является использование объекта Bundle или BundleContext.
// If you have a BundleContext
Dictionary headers = bundleContext.getBundle().getHeaders();
// If you don't have a context, and are running in 4.2
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
bundle.getHeaders();
Обратите внимание, что объект Bundle также предоставляет getEntry(String path)
для поиска ресурсов, содержащихся в определенном пакете, вместо поиска этого пакета для всего пути к классам.
В общем случае, если вы хотите получить информацию, связанную с пакетом, не полагайтесь на предположения о загрузчиках классов, просто используйте API OSGi напрямую.
Ответ 5
Следующий код работает с несколькими типами архивов (jar, war) и несколькими типами загрузчиков классов (jar, url, vfs,...)
public static Manifest getManifest(Class<?> clz) {
String resource = "/" + clz.getName().replace(".", "/") + ".class";
String fullPath = clz.getResource(resource).toString();
String archivePath = fullPath.substring(0, fullPath.length() - resource.length());
if (archivePath.endsWith("\\WEB-INF\\classes") || archivePath.endsWith("/WEB-INF/classes")) {
archivePath = archivePath.substring(0, archivePath.length() - "/WEB-INF/classes".length()); // Required for wars
}
try (InputStream input = new URL(archivePath + "/META-INF/MANIFEST.MF").openStream()) {
return new Manifest(input);
} catch (Exception e) {
throw new RuntimeException("Loading MANIFEST for class " + clz + " failed!", e);
}
}
Ответ 6
Вы можете использовать getProtectionDomain(). getCodeSource() следующим образом:
URL url = Menu.class.getProtectionDomain().getCodeSource().getLocation();
File file = DataUtilities.urlToFile(url);
JarFile jar = null;
try {
jar = new JarFile(file);
Manifest manifest = jar.getManifest();
Attributes attributes = manifest.getMainAttributes();
return attributes.getValue("Built-By");
} finally {
jar.close();
}
Ответ 7
Самый простой способ - использовать класс JarURLConnection:
String className = getClass().getSimpleName() + ".class";
String classPath = getClass().getResource(className).toString();
if (!classPath.startsWith("jar")) {
return DEFAULT_PROPERTY_VALUE;
}
URL url = new URL(classPath);
JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
Manifest manifest = jarConnection.getManifest();
Attributes attributes = manifest.getMainAttributes();
return attributes.getValue(PROPERTY_NAME);
Потому что в некоторых случаях ...class.getProtectionDomain().getCodeSource().getLocation();
задает путь с vfs:/
, поэтому это нужно обработать дополнительно.
Ответ 8
Почему вы включаете шаг getClassLoader? Если вы скажете "this.getClass(). GetResource()", вы должны получать ресурсы по отношению к вызывающему классу. Я никогда не использовал ClassLoader.getResource(), хотя из быстрого просмотра документов Java это похоже на то, что вы получите первый ресурс этого имени, найденный в любом текущем пути к классам.
Ответ 9
public static Manifest getManifest( Class<?> cl ) {
InputStream inputStream = null;
try {
URLClassLoader classLoader = (URLClassLoader)cl.getClassLoader();
String classFilePath = cl.getName().replace('.','/')+".class";
URL classUrl = classLoader.getResource(classFilePath);
if ( classUrl==null ) return null;
String classUri = classUrl.toString();
if ( !classUri.startsWith("jar:") ) return null;
int separatorIndex = classUri.lastIndexOf('!');
if ( separatorIndex<=0 ) return null;
String manifestUri = classUri.substring(0,separatorIndex+2)+"META-INF/MANIFEST.MF";
URL url = new URL(manifestUri);
inputStream = url.openStream();
return new Manifest( inputStream );
} catch ( Throwable e ) {
// handle errors
...
return null;
} finally {
if ( inputStream!=null ) {
try {
inputStream.close();
} catch ( Throwable e ) {
// ignore
}
}
}
}
Ответ 10
Я использовал решение от Anthony Juckel, но в MANIFEST.MF ключ должен начинаться с прописного.
Итак, мой файл MANIFEST.MF содержит ключ вроде:
Значение Mykey:
Затем в активаторе или другом классе вы можете использовать код от Anthony, чтобы прочитать файл MANIFEST.MF и нужное значение.
// If you have a BundleContext
Dictionary headers = bundleContext.getBundle().getHeaders();
// If you don't have a context, and are running in 4.2
Bundle bundle = `FrameworkUtil.getBundle(this.getClass());
bundle.getHeaders();
Ответ 11
Я соглашусь с тем, что этот ответ не отвечает на первоначальный вопрос, который обычно имеет доступ к Манифесту. Однако, если действительно требуется прочитать один из нескольких "стандартных" атрибутов Manifest, следующее решение намного проще, чем те, которые были выше. Поэтому я надеюсь, что модератор это позволит. Обратите внимание, что это решение находится в Kotlin, а не Java, но я бы ожидал, что порт для Java будет тривиальным. (Хотя я признаю, что не знаю эквивалент Java ".package".
В моем случае я хотел прочитать атрибут "Версия реализации", поэтому я начал с приведенных выше решений, чтобы получить поток, а затем прочитать его для получения значения. Хотя это решение сработало, коллега, просматривая мой код, показал мне более простой способ сделать то, что я хотел. Обратите внимание, что это решение находится в Kotlin, а не в Java.
val myPackage = MyApplication::class.java.'package'
val implementationVersion = myPackage.implementationVersion
Еще раз обратите внимание, что это не отвечает на исходный вопрос, в частности "Export-package" не является одним из поддерживаемых атрибутов. Тем не менее, есть myPackage.name, которое возвращает значение. Возможно, кто-то, кто понимает это больше, чем я могу прокомментировать, возвращает ли это значение, которое запрашивает оригинальный плакат.
Ответ 12
У меня есть это странное решение, которое запускает военные приложения на встроенном сервере Jetty, но эти приложения также должны запускаться на стандартных серверах Tomcat, и у нас есть некоторые специальные свойства в manfest.
Проблема заключалась в том, что когда в Tomcat манифест можно было прочитать, но когда в причале был выбран случайный манифест (который пропустил специальные свойства)
Основываясь на ответе Алекса Коншина, я придумал следующее решение (входной поток затем используется в классе Manifest):
private static InputStream getWarManifestInputStreamFromClassJar(Class<?> cl ) {
InputStream inputStream = null;
try {
URLClassLoader classLoader = (URLClassLoader)cl.getClassLoader();
String classFilePath = cl.getName().replace('.','/')+".class";
URL classUrl = classLoader.getResource(classFilePath);
if ( classUrl==null ) return null;
String classUri = classUrl.toString();
if ( !classUri.startsWith("jar:") ) return null;
int separatorIndex = classUri.lastIndexOf('!');
if ( separatorIndex<=0 ) return null;
String jarManifestUri = classUri.substring(0,separatorIndex+2);
String containingWarManifestUri = jarManifestUri.substring(0,jarManifestUri.indexOf("WEB-INF")).replace("jar:file:/","file:///") + MANIFEST_FILE_PATH;
URL url = new URL(containingWarManifestUri);
inputStream = url.openStream();
return inputStream;
} catch ( Throwable e ) {
// handle errors
LOGGER.warn("No manifest file found in war file",e);
return null;
}
}