Чтение Манифеста моего кувшина
Мне нужно прочитать Manifest
файл, который доставил мой класс, но когда я использую:
getClass().getClassLoader().getResources(...)
Я получаю MANIFEST
от первой .jar
загружен в среду выполнения Java.
Мое приложение будет запущено из апплета или веб-запуска,
так что я не буду иметь доступ к своему собственному .jar
файл, я думаю.
Я на самом деле хочу прочитать Export-package
атрибут из .jar
который запустил Felix OSGi, поэтому я могу представить эти пакеты Felix. Есть идеи?
12 ответов
Вы можете сделать одну из двух вещей:
Вызов
getResources()
and iterate through the returned collection of URLs, reading them as manifests until you find yours: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 } }
You can try checking whether
getClass().getClassLoader()
это примерjava.net.URLClassLoader
, Majority of Sun classloaders are, includingAppletClassLoader
, You can then cast it and callfindResource()
which has been known - for applets, at least - to return the needed manifest directly: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 }
Сначала вы можете найти 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");
Ты можешь использовать Manifests
из jcabi-manifest и считайте любой атрибут из любого из доступных файлов 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
Я сразу признаю, что этот ответ не отвечает на первоначальный вопрос о том, чтобы вообще иметь доступ к Манифесту. Однако, если действительно необходимо прочитать один из нескольких "стандартных" атрибутов Manifest, следующее решение будет намного проще, чем те, которые были опубликованы выше. Поэтому я надеюсь, что модератор это допустит. Обратите внимание, что это решение на Kotlin, а не на Java, но я ожидаю, что порт на Java будет тривиальным. (Хотя я признаю, что не знаю Java-эквивалент ".`package`".
В моем случае я хотел прочитать атрибут "Реализация-версия", поэтому я начал с решений, приведенных выше, чтобы получить поток, а затем прочитал его, чтобы получить значение. Пока это решение работало, коллега, просматривавший мой код, показал мне более простой способ сделать то, что я хотел. Обратите внимание, что это решение на Kotlin, а не на Java.
val myPackage = MyApplication::class.java.`package`
val implementationVersion = myPackage.implementationVersion
Еще раз отметим, что это не отвечает на первоначальный вопрос, в частности "Export-package" не представляется одним из поддерживаемых атрибутов. Тем не менее, есть myPackage.name, которое возвращает значение. Возможно, кто-то, кто понимает это больше, чем я, может прокомментировать, возвращает ли это значение, запрашиваемое оригинальным постером.
Самый простой способ - использовать класс 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:/
, так что это должно быть обработано дополнительно.
Я считаю, что наиболее подходящим способом получения манифеста для любого пакета (включая пакет, который загрузил данный класс) является использование объекта 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 напрямую.
Следующий код работает с несколькими типами архивов (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);
}
}
Более простой способ сделать это - использовать getPackage(). Например, чтобы получить версию реализации:
Application.class.getPackage().getImplementationVersion()
Вы можете использовать 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();
}
Почему вы включаете шаг getClassLoader? Если вы говорите "this.getClass(). GetResource()", вы должны получать ресурсы относительно вызывающего класса. Я никогда не использовал ClassLoader.getResource(), хотя при быстром взгляде на Java Docs кажется, что вы получите первый ресурс с таким именем, найденный в любом текущем пути к классам.
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
}
}
}
}
Я использовал решение от Энтони Джакеля, но в MANIFEST.MF ключ должен начинаться с заглавной буквы.
Так что мой файл MANIFEST.MF содержит такой ключ:
Mykey: значение
Затем в активаторе или другом классе вы можете использовать код от Энтони, чтобы прочитать файл 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();
У меня есть это странное решение, которое запускает военные приложения на встроенном сервере Jetty, но эти приложения также должны работать на стандартных серверах Tomcat, и у нас есть некоторые специальные свойства в manfest.
Проблема заключалась в том, что в Tomcat можно было прочитать манифест, а в пристани был выбран случайный манифест (в котором отсутствовали специальные свойства).
Основываясь на ответе Алекса Коншина, я нашел следующее решение (затем входной поток используется в классе манифеста):
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;
}
}