Lendo o manifesto do meu próprio frasco

134

Eu preciso ler o Manifestarquivo, que entregou minha classe, mas quando eu uso:

getClass().getClassLoader().getResources(...)

Eu recebo o MANIFESTprimeiro .jarcarregado no Java Runtime.
Meu aplicativo será executado a partir de um miniaplicativo ou de um webstart,
portanto , acho que não terei acesso ao meu próprio .jararquivo.

Na verdade, eu quero ler o Export-packageatributo a partir do .jarqual o Felix OSGi iniciou, para que eu possa expor esses pacotes ao Felix. Alguma ideia?

Houtman
fonte
3
Eu acho que a resposta FrameworkUtil.getBundle () abaixo é a melhor. Ele responde ao que você realmente deseja fazer (obter as exportações do pacote) e não ao que você pediu (leia o manifesto).
22812 Chris Dolan

Respostas:

117

Você pode fazer uma das duas coisas:

  1. Ligue getResources()e repita a coleção retornada de URLs, lendo-os como manifestos até encontrar o seu:

    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
        }
    }
    
  2. Você pode tentar verificar se getClass().getClassLoader()é uma instância de java.net.URLClassLoader. A maioria dos carregadores de classes da Sun são, inclusive AppletClassLoader. Em seguida, você pode convertê-lo e chamar o findResource()que é conhecido - pelo menos para applets - para retornar diretamente o manifesto necessário:

    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
    }
    
ChssPly76
fonte
5
Perfeito! Eu nunca soube que você poderia iterar através de recursos com o mesmo nome.
Houtman
Como você sabe que o carregador de classes só conhece um único arquivo .jar? (verdadeiro em muitos casos, suponho). Prefiro usar algo associado diretamente à classe em questão.
21730 Jason S
7
é uma boa prática fazer respostas separadas para cada uma, em vez de incluir as duas correções em uma resposta. Respostas separadas podem ser votadas independentemente.
Alba Mendez
apenas uma observação: eu precisava de algo semelhante, mas estou dentro de uma WAR no JBoss, portanto a segunda abordagem não funcionou para mim. Acabei com uma variante de stackoverflow.com/a/1283496/160799
Gregor
1
A primeira opção não funcionou para mim. Eu tenho os manifestos dos meus 62 frascos de dependência, mas não aquele onde a classe atual foi definida ...
Jolta
120

Você pode encontrar o URL da sua turma primeiro. Se for um JAR, você carregará o manifesto a partir daí. Por exemplo,

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");
ZZ Coder
fonte
Gosto dessa solução, pois ela obtém seu próprio manifesto diretamente, em vez de precisar procurá-la.
Jay
1
pode ser melhorado um pouco por cheque condição removendoclassPath.replace("org/example/MyClass.class", "META-INF/MANIFEST.MF"
Jay
2
Quem fecha o fluxo?
ceving 03/12/2013
1
Isso não funciona nas classes internas, porque getSimpleNameremove o nome da classe externa. Isto irá funcionar para classes internas: clazz.getName().replace (".", "/") + ".class".
ceving 03/12/2013
3
Você precisa fechar o fluxo, o construtor de manifesto não.
BrianT.
21

Você pode usar Manifestsde jcabi-manifestests e ler qualquer atributo de qualquer um dos arquivos MANIFEST.MF disponíveis com apenas uma linha:

String value = Manifests.read("My-Attribute");

A única dependência que você precisa é:

<dependency>
  <groupId>com.jcabi</groupId>
  <artifactId>jcabi-manifests</artifactId>
  <version>0.7.5</version>
</dependency>

Além disso, consulte esta publicação no blog para obter mais detalhes: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

yegor256
fonte
Bibliotecas muito agradáveis. Existe uma maneira de controlar o nível de log?
assylias 30/08/13
1
Todas as bibliotecas jcabi fazem logon no SLF4J. Você pode enviar mensagens de log usando qualquer instalação que você deseja, por exemplo log4j ou logback
yegor256
se usando logback.xml a linha que você precisa adicionar é como<logger name="com.jcabi.manifests" level="OFF"/>
driftcatcher
1
Várias manifestos a partir da mesma sobreposição carregador de classe e sobrepor uns aos outros
guai
13

Admito desde já que esta resposta não responde à pergunta original, a de geralmente poder acessar o Manifesto. No entanto, se o que é realmente necessário é ler um dos vários atributos "padrão" do Manifest, a solução a seguir é muito mais simples do que as postadas acima. Então, espero que o moderador permita. Observe que esta solução está no Kotlin, não no Java, mas eu esperaria que uma porta para Java fosse trivial. (Embora eu admita que não conheço o equivalente em Java de ".`package`".

No meu caso, eu queria ler o atributo "Implementation-Version", então comecei com as soluções fornecidas acima para obter o fluxo e, em seguida, li-o para obter o valor. Enquanto essa solução funcionava, um colega de trabalho revisando meu código me mostrou uma maneira mais fácil de fazer o que eu queria. Observe que esta solução está no Kotlin, não no Java.

val myPackage = MyApplication::class.java.`package`
val implementationVersion = myPackage.implementationVersion

Mais uma vez, observe que isso não responde à pergunta original, em particular "Export-package" não parece ser um dos atributos suportados. Dito isto, existe um myPackage.name que retorna um valor. Talvez alguém que entenda isso mais do que eu possa comentar se isso retorna o valor que o pôster original está solicitando.

Steven W. Klassen
fonte
4
Na verdade, o porto java é simples:String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();
Ian Robertson
É verdade que era isso que eu estava procurando. Também estou feliz que o Java também tenha um equivalente.
Aleksander Stelmaczonek 15/01/19
12

Acredito que a maneira mais apropriada de obter o manifesto para qualquer pacote (incluindo o pacote que carregou uma determinada classe) é usar o objeto Bundle ou 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();

Observe que o objeto Bundle também fornece getEntry(String path)a consulta de recursos contidos em um pacote específico, em vez de procurar o caminho de classe inteiro desse pacote.

Em geral, se você quiser informações específicas de pacote configurável, não confie em suposições sobre os carregadores de classes, basta usar as APIs OSGi diretamente.

Anthony Juckel
fonte
9

O código a seguir funciona com vários tipos de arquivos (jar, war) e vários tipos de classloaders (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);
    }
  }
muellair
fonte
pode resultar de clz.getResource(resource).toString()barras invertidas?
bacia
9

A maneira mais fácil é usar a classe 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);

Como, em alguns casos, ...class.getProtectionDomain().getCodeSource().getLocation();fornece caminho para vfs:/, isso deve ser tratado adicionalmente.

ayurchuk
fonte
Essa é, de longe, a maneira mais fácil e limpa de fazer isso.
walen 04/07/19
6

Você pode usar getProtectionDomain (). GetCodeSource () assim:

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();
}
Uto
fonte
1
getCodeSourcepode retornar null. Quais são os critérios para que isso funcione? A documentação não explica isso.
ceving 03/12/2013
4
De onde é DataUtilitiesimportado? Não parece estar no JDK.
Jolta
2

Por que você está incluindo a etapa getClassLoader? Se você disser "this.getClass (). GetResource ()", você deverá obter recursos relativos à classe de chamada. Eu nunca usei ClassLoader.getResource (), embora, a partir de uma rápida olhada no Java Docs, pareça que você obterá o primeiro recurso desse nome encontrado em qualquer caminho de classe atual.

Jay
fonte
Se sua classe for denominada "com.mypackage.MyClass", a chamada class.getResource("myresource.txt")tentará carregar esse recurso com/mypackage/myresource.txt. Como exatamente você vai usar essa abordagem para obter o manifesto?
ChssPly76
1
Ok, eu tenho que voltar atrás. Isso é o que resulta de não testar. Eu estava pensando que você poderia dizer this.getClass (). GetResource ("../../ META-INF / MANIFEST.MF") (No entanto, muitos ".." são necessários, dado o nome do seu pacote.) Mas enquanto que funciona para arquivos de classe em um diretório para subir uma árvore de diretórios, aparentemente não funciona para JARs. Não vejo por que não, mas é assim que é. Isso também não funciona.getClass (). GetResource ("/ META-INF / MANIFEST.MF") - isso me dá o manifesto do rt.jar. (Continua ...)
Jay
O que você pode fazer é usar o getResource para encontrar o caminho para seu próprio arquivo de classe e remover tudo após o "!" para obter o caminho para o jar, anexe "/META-INF/MANIFEST.MF". Como Zhihong sugeriu, estou votando nele.
Jay
1
  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
        }
      }
    }
  }
Alex Konshin
fonte
Esta resposta usa uma maneira muito complexa e propensa a erros de carregar o manifesto. a solução muito mais simples é usar cl.getResourceAsStream("META-INF/MANIFEST.MF").
Robert
Você tentou? Qual manifesto de jar obterá se você tiver vários jarros no caminho de classe? Vai levar o primeiro que não é o que você precisa. Meu código resolve esse problema e realmente funciona.
Alex Konshin
Não critiquei a maneira como você é o carregador de classes para carregar um recurso específico. Eu estava apontando que todo o código entre classLoader.getResource(..)e url.openStream()é totalmente irrelevante e propenso a erros, pois ele tenta fazer o mesmo que classLoader.getResourceAsStream(..)faz.
Robert
Não. É diferente. Meu código se manifesta no jar específico em que a classe está localizada, e não no primeiro jar no caminho de classe.
Alex Konshin
O seu "código de carregamento específico do jar" é equivalente às duas linhas a seguir:ClassLoader classLoader = cl.getClassLoader(); return new Manifest(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF"));
Robert
0

Eu usei a solução de Anthony Juckel, mas no MANIFEST.MF a chave deve começar com maiúsculas.

Portanto, meu arquivo MANIFEST.MF contém uma chave como:

Mykey: value

Em seguida, no ativador ou em outra classe, você pode usar o código de Anthony para ler o arquivo MANIFEST.MF e o valor necessário.

// 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();
user2935659
fonte
0

Eu tenho essa solução estranha que executa aplicativos de guerra em um servidor Jetty incorporado, mas esses aplicativos também precisam ser executados em servidores Tomcat padrão, e temos algumas propriedades especiais no manifesto.

O problema era que, no Tomcat, o manifesto podia ser lido, mas, no cais, um manifesto aleatório era capturado (perdendo as propriedades especiais)

Com base na resposta de Alex Konshin, eu vim com a seguinte solução (o fluxo de entrada é então usado em uma classe 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;
    }
}
GriffoGoes
fonte