내 항아리의 매니페스트 읽기


134

Manifest클래스를 전달한 파일 을 읽어야 하지만 다음을 사용할 때 :

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

Java Runtime에 MANIFEST처음 .jar로드 된 것을 얻습니다 .
내 응용 프로그램이 애플릿 또는 웹 스타트에서 실행
되므로 내 .jar파일에 액세스 할 수 없습니다 .

실제로 Felix OSGi를 시작한 Export-package속성 을 읽고 싶습니다 .jar. 따라서 해당 패키지를 Felix에 공개 할 수 있습니다. 어떤 아이디어?


3
아래의 FrameworkUtil.getBundle () 답변이 최고라고 생각합니다. 요청한 것 (매니페스트를 읽는 것)이 아니라 실제로 원하는 것 (번들 내보내기를 가져옵니다)에 응답합니다 .
— Chris Dolan

답변:


117

다음 두 가지 중 하나를 수행 할 수 있습니다.

  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
        }
    }
  2. 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
    }

5
완전한! 나는 당신이 같은 이름의 리소스를 반복 할 수 있다는 것을 결코 알지 못했습니다.
— 호우 트먼

클래스 로더가 단일 .jar 파일 만 알고 있다는 것을 어떻게 알 수 있습니까? (많은 경우에 사실이라고 생각합니다) 나는 문제의 클래스와 직접 관련된 것을 오히려 오히려 사용하려고합니다.
— Jason S

7
하나의 답변에 2 개의 수정 사항을 포함시키는 대신 각각에 대해 별도의 답변 을 작성 하는 것이 좋습니다 . 별도의 답변은 독립적으로 투표 할 수 있습니다.
— Alba Mendez

참고 사항 : 비슷한 것이 필요했지만 JBoss의 WAR에있어 두 번째 접근 방식이 효과가 없었습니다. 나는 stackoverflow.com/a/1283496/160799
— Gregor

1
첫 번째 옵션은 저에게 효과적이지 않았습니다. 나는 62 개의 의존성 jar의 매니페스트를 얻었지만 현재 클래스가 정의 된 것은 아니다.
— Jolta

120

수업의 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");

이 솔루션은 검색하지 않고 직접 매니페스트를 가져 오기 때문에이 솔루션이 마음에 듭니다.
— Jay

1
상태 점검을 제거하여 조금 개선 될 수 있습니다classPath.replace("org/example/MyClass.class", "META-INF/MANIFEST.MF"
— Jay

2
스트림을 누가 닫습니까?
— ceving

1
getSimpleName외부 클래스 이름을 제거 하기 때문에 내부 클래스에서는 작동하지 않습니다 . 이것은 내부 클래스에서 작동합니다 clazz.getName().replace (".", "/") + ".class".
— ceving

3
스트림을 닫아야하지만 매니페스트 생성자는 그렇지 않습니다.
— BrianT.

21

jcabi-manifestsManifests 에서 사용할 수 있으며 사용 가능한 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


아주 좋은 도서관. 로그 레벨을 제어하는 ​​방법이 있습니까?
— assylias

1
모든 jcabi 라이브러리는 SLF4J를 통해 기록됩니다. 원하는 기능을 사용하여 로그 메시지를
— 발송할

logback.xml을 사용하는 경우 추가해야 할 줄은 다음과 같습니다.<logger name="com.jcabi.manifests" level="OFF"/>
— driftcatcher

1
동일한 클래스 로더의 여러 매니페스트가 서로 겹치고 겹쳐 쓰기
— guai

13

이 답변이 원래 질문, 일반적으로 매니페스트에 액세스 할 수있는 질문에 답변하지 않는다는 점을 인정합니다. 그러나 실제로 필요한 것이 많은 "표준"매니페스트 속성 중 하나를 읽는 것이라면 다음 솔루션은 위에 게시 된 것보다 훨씬 간단합니다. 그래서 중재자가 허락하기를 바랍니다. 이 솔루션은 Java가 아닌 Kotlin에 있지만 Java 포트는 사소한 것으로 예상합니다. (나는 ".`package`"와 같은 Java를 모른다는 것을 인정하지만.

필자의 경우 "Implementation-Version"속성을 읽고 스트림을 얻기 위해 위에서 주어진 솔루션으로 시작한 다음 값을 얻기 위해 읽었습니다. 이 솔루션이 작동하는 동안 내 코드를 검토하는 동료가 원하는 것을 쉽게 수행 할 수있는 방법을 보여주었습니다. 이 솔루션은 Java가 아닌 Kotlin에 있습니다.

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

다시 한 번 이것은 원래 질문에 대한 답변이 아니며 특히 "내보내기 패키지"는 지원되는 속성 중 하나가 아닌 것 같습니다. 즉, 값을 반환하는 myPackage.name이 있습니다. 어쩌면 이것을 이해하는 사람은 원래 포스터가 요구하는 가치를 반환하는지 여부에 대해 언급 할 수 있습니다.


4
실제로, 자바 포트는 간단하다 :String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();
— Ian Robertson

사실 이것이 내가 찾던 것입니다. 또한 Java에도 동등한 기능이 있습니다.
— Aleksander Stelmaczonek

12

특정 번들을로드 한 번들을 포함하여 모든 번들의 매니페스트를 얻는 가장 적절한 방법은 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)해당 번들의 전체 클래스 경로를 검색하지 않고 특정 번들에 포함 된 리소스를 조회 할 수도 있습니다 .

일반적으로 번들 특정 정보를 원하는 경우 클래스 로더에 대한 가정에 의존하지 말고 OSGi API를 직접 사용하십시오.


9

다음 코드는 여러 유형의 아카이브 (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);
    }
  }

clz.getResource(resource).toString()백 슬래시 가 발생할 수 있습니까?
— 분지

9

가장 쉬운 방법은 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:/하므로 추가로 처리해야합니다.


이것은 지금까지 가장 쉽고 깨끗한 방법입니다.
— walen

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();
}

1
getCodeSource반환 될 수 있습니다 null. 이것이 작동하는 기준은 무엇입니까? 문서는 이것을 설명하지 않습니다.
— 15

4
어디에서 DataUtilities수입합니까? JDK에없는 것 같습니다.
— Jolta

2

왜 getClassLoader 단계를 포함하고 있습니까? "this.getClass (). getResource ()"라고하면 호출 클래스와 관련된 리소스를 가져와야합니다. JavaLoader를 간략히 살펴보면 ClassLoader.getResource ()를 사용한 적이 없지만 현재 클래스 경로에서 찾은 해당 이름의 첫 번째 리소스를 얻는 것처럼 들립니다.


클래스 이름이 "com.mypackage.MyClass"인 경우 호출 class.getResource("myresource.txt")하면에서 해당 리소스를로드하려고합니다 com/mypackage/myresource.txt. 이 접근법을 정확히 사용하여 매니페스트를 얻는 방법은 무엇입니까?
— ChssPly76

1
좋아, 역 추적해야 해 그것이 테스트하지 않은 결과입니다. this.getClass (). getResource ( "../../ META-INF / MANIFEST.MF")라고 말할 수 있다고 생각했습니다 (패키지 이름에 ".."가 많이 필요합니다.) 디렉토리의 클래스 파일이 디렉토리 트리에서 작동하도록 작동하지만 JAR에서는 작동하지 않습니다. 왜 그런지 모르겠지만, 그것이 그 방법입니다. this.getClass (). getResource ( "/ META-INF / MANIFEST.MF")도 작동하지 않습니다. rt.jar의 매니페스트가 표시됩니다. (계속 ...)
— Jay

할 수있는 일은 getResource를 사용하여 자신의 클래스 파일에 대한 경로를 찾은 다음 "!"뒤의 모든 것을 제거하는 것입니다. jar 경로를 얻으려면 "/META-INF/MANIFEST.MF"를 추가하십시오. Zhihong이 제안한 것처럼, 나는 그를 투표하고 있습니다.
— 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
        }
      }
    }
  }

이 답변은 매니페스트를로드하는 매우 복잡하고 오류가 발생하기 쉬운 방법을 사용합니다. 훨씬 간단한 해결책은 사용하는 것 cl.getResourceAsStream("META-INF/MANIFEST.MF")입니다.
— Robert

해봤 어? 클래스 패스에 여러 개의 항아리가있는 경우 어떤 항아리가 나타 납니까? 필요한 것이 아닌 첫 번째 것이 필요합니다. 내 코드는이 문제를 해결하고 실제로 작동합니다.
— Alex Konshin

특정 리소스를로드하는 클래스 로더를 어떻게 사용하는지 비판하지 않았습니다. 나는 모든 코드 사이 classLoader.getResource(..)와 url.openStream()완전히 관련이 없으며 오류가 발생하기 쉽다는 것을 지적했다 classLoader.getResourceAsStream(..).
— Robert

아니. 다릅니다. 내 코드는 클래스 경로의 첫 번째 항아리가 아니라 클래스가있는 특정 항아리에서 나타납니다.
— Alex Konshin

"jar 특정 로딩 코드"는 다음 두 줄과 같습니다.ClassLoader classLoader = cl.getClassLoader(); return new Manifest(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF"));
— Robert

0

Anthony Juckel의 솔루션을 사용했지만 MANIFEST.MF에서 키는 대문자로 시작해야합니다.

따라서 내 MANIFEST.MF 파일에는 다음과 같은 키가 포함되어 있습니다.

마이키 : 가치

그런 다음 활성화 기 또는 다른 클래스에서 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();

0

임베디드 Jetty 서버에서 war 응용 프로그램을 실행하는 이상한 솔루션이 있지만이 응용 프로그램은 표준 Tomcat 서버에서도 실행해야하며 manfest에는 몇 가지 특별한 속성이 있습니다.

문제는 Tomcat에서 매니페스트를 읽을 수 있지만 부두에서 임의 매니페스트가 선택되었다는 것입니다 (특별한 속성이 누락되었습니다)

Alex Konshin의 답변을 바탕으로 다음 솔루션을 제안했습니다 (입력 스트림은 매니페스트 클래스에서 사용됨).

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;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.