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


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.