【问题标题】:How do you create a MANIFEST.MF that's available when you're testing and running from a jar in production?当您在生产环境中测试和运行 jar 时,如何创建可用的 MANIFEST.MF?
【发布时间】:2010-09-10 05:08:06
【问题描述】:

我花了太多时间试图弄清楚这一点。这应该是最简单的事情,每个在 jar 中分发 Java 应用程序的人都必须处理它。

我只想知道将版本控制添加到我的 Java 应用程序的正确方法,以便我在测试时可以访问版本信息,例如在 Eclipse 中调试从 jar 中运行。

这是我的 build.xml 中的内容:

<target name="jar" depends = "compile">
    <property name="version.num" value="1.0.0"/>
    <buildnumber file="build.num"/>
    <tstamp>
        <format property="TODAY" pattern="yyyy-MM-dd HH:mm:ss" />
    </tstamp>

    <manifest file="${build}/META-INF/MANIFEST.MF">
        <attribute name="Built-By" value="${user.name}" />
        <attribute name="Built-Date" value="${TODAY}" />                   
        <attribute name="Implementation-Title" value="MyApp" />
        <attribute name="Implementation-Vendor" value="MyCompany" />                
        <attribute name="Implementation-Version" value="${version.num}-b${build.number}"/>                              
    </manifest>

    <jar destfile="${build}/myapp.jar" basedir="${build}" excludes="*.jar" />                   
</target>

这会创建 /META-INF/MANIFEST.MF,因此我在 Eclipse 中调试时可以读取这些值:

public MyClass()
{
    try
    {                        
        InputStream stream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");
        Manifest manifest = new Manifest(stream);            

        Attributes attributes = manifest.getMainAttributes();

        String implementationTitle = attributes.getValue("Implementation-Title");
        String implementationVersion = attributes.getValue("Implementation-Version");
        String builtDate = attributes.getValue("Built-Date");
        String builtBy = attributes.getValue("Built-By");
   }
   catch (IOException e)
   {            
        logger.error("Couldn't read manifest.");
   }        

}

但是,当我创建 jar 文件时,它会加载另一个 jar 的清单(大概是应用程序加载的第一个 jar - 在我的例子中是 activation.jar)。

此外,尽管所有正确的值都在清单文件中,但以下代码也不起作用。

    Package thisPackage = getClass().getPackage();
    String implementationVersion = thisPackage.getImplementationVersion();

有什么想法吗?

【问题讨论】:

    标签: java ant jar versioning manifest.mf


    【解决方案1】:

    您可以使用来自jcabi-manifests 的实用程序类Manifests,它可以自动查找和解析类路径中所有可用的MANIFEST.MF 文件。然后,您可以使用单行读取任何属性:

    final String name = Manifests.read("Build-By");
    final String date = Manifests.read("Build-Date");
    

    另外,请查看:http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

    【讨论】:

    • 上面显示的解决方案仅适用于单个清单或至少适用于清单文件中的唯一属性值。假设构建日期包含在几个 JAR 中,并且正如您所说,所有这些 JAR 都已读取。将返回哪个“构建日期”属性?
    【解决方案2】:

    你想用这个:

    Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");
    

    您可以解析 URL 以确定清单是否来自哪个 jar,然后通过 getInputStream() 读取 URL 以解析清单。

    【讨论】:

    • 如果在 ContextClassLoader 的类路径中您有多个带有清单文件的源,则会出现问题。
    【解决方案3】:

    您可以在任意 jar 中获取任意类的清单,而无需解析类 url(这可能很脆弱)。只需在所需的 jar 中找到您知道的资源,然后将连接强制转换为 JarURLConnection。

    如果您希望代码在类未捆绑在 jar 中时工作,请添加一个 instanceof 检查返回的 URL 连接类型。未打包类层次结构中的类将返回内部 Sun FileURLConnection 而不是 JarUrlConnection。然后,您可以使用其他答案中描述的 InputStream 方法之一加载 Manifest。

    @Test
    public void testManifest() throws IOException {
        URL res = org.junit.Assert.class.getResource(org.junit.Assert.class.getSimpleName() + ".class");
        JarURLConnection conn = (JarURLConnection) res.openConnection();
        Manifest mf = conn.getManifest();
        Attributes atts = mf.getMainAttributes();
        for (Object v : atts.values()) {
            System.out.println(v);
        }
    }
    

    【讨论】:

    • 很好,谢谢。我在这里尝试了一切,在(目前 3 个)答案中,承认 Class.getResource() 经常找到错误的清单(在错误的 jar 中)并提供找到正确清单的解决方案,我最喜欢这个。跨度>
    • 如果你想要特定的东西,请使用atts.getValue("Attribute-Name")atts.get(new Attributes.Name("Attribute-Name")),非通用代码 FTW!
    • 仅供参考,如果在 IDE 中使用,例如对于测试,类型转换为JarURLConnection 会引发引导类加载器异常。但是,一旦该类包含在构建的 JAR 中,它就可以工作。谢谢。
    【解决方案4】:

    我发现 McDowell 的评论是正确的——选择哪个 MANIFEST.MF 文件取决于类路径,可能不是想要的。我用这个

    String cp = PCAS.class.getResource(PCAS.class.getSimpleName() + ".class").toString();
    cp = cp.substring(0, cp.indexOf(PCAS.class.getPackage().getName())) 
                  +  "META-INF/MANIFEST.MF";
    Manifest mf = new Manifest((new URL(cp)).openStream());
    

    改编自link text

    【讨论】:

    • 这几乎对我有用,尽管指向您从中获取此内容的链接现在已损坏。我说“几乎”是因为 Class.getPackage() 返回一个点分隔的名称 (org.foo.bar),而 Class.getSimpleName() 返回一个斜杠分隔的名称 (org/foo/bar)。正因为如此,我喜欢 gibbss 的回答,它避免了解析类 url。
    【解决方案5】:

    ClassLoader.getResource(String) 将加载它在类路径中找到的第一个清单,这可能是其他一些 JAR 文件的清单。因此,您可以通过enumerate all the manifests 找到您想要的文件或使用其他机制,例如具有唯一名称的属性文件。

    【讨论】:

    • 你说得对,getResource() 经常会找到错误的清单;我正在经历那个;我尝试了您指向 ClassLoader.getResources() 的链接以枚举所有清单,但 getResources("/META-INF/MANIFEST.MF") 什么也不返回,而 getResources("") 返回的结果不仅没有,但也没有用。我怀疑我使用了错误的类加载器,但是“枚举所有清单”会演变为“枚举所有类加载器”!
    【解决方案6】:

    如果您使用与加载类相同的类加载器,则可以访问 jar 中的清单(或任何其他)文件。

    this.getClass().getClassLoader().getResourceAsStream( ... ) ;
    

    如果您是多线程的,请使用以下内容:

    Thread.currentThread().getContextClassLoader().getResourceAsStream( ... ) ;
    

    这也是在 jar 中包含默认配置文件的一种非常有用的技术。

    【讨论】:

      【解决方案7】:

      这是我发现的有效方法:

      packageVersion.java:

      package com.company.division.project.packageversion;
      
      import java.io.IOException;
      import java.io.InputStream;
      import java.util.jar.Attributes;
      import java.util.jar.Manifest;
      
      public class packageVersion
      {
          void printVersion()
          {
              try
              {         
                  InputStream stream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");
      
                  if (stream == null)
                  {
                      System.out.println("Couldn't find manifest.");
                      System.exit(0);
                  }
      
                  Manifest manifest = new Manifest(stream);
      
                  Attributes attributes = manifest.getMainAttributes();
      
                  String impTitle = attributes.getValue("Implementation-Title");
                  String impVersion = attributes.getValue("Implementation-Version");
                  String impBuildDate = attributes.getValue("Built-Date");
                  String impBuiltBy = attributes.getValue("Built-By");
      
                  if (impTitle != null)
                  {
                      System.out.println("Implementation-Title:   " + impTitle);
                  }            
                  if (impVersion != null)
                  {
                      System.out.println("Implementation-Version: " + impVersion);
                  }
                  if (impBuildDate != null)
                  {
                      System.out.println("Built-Date: " + impBuildDate);
                  }
                  if (impBuiltBy != null)
                  {
                      System.out.println("Built-By:   " + impBuiltBy);
                  }
      
                  System.exit(0);
              }
              catch (IOException e)
              {            
                  System.out.println("Couldn't read manifest.");
              }        
          }
      
          /**
           * @param args
           */
          public static void main(String[] args)
          {
              packageVersion version = new packageVersion();
              version.printVersion();        
          }
      
      }
      

      这是匹配的 build.xml:

      <project name="packageVersion" default="run" basedir=".">
      
          <property name="src" location="src"/>
          <property name="build" location="bin"/>
          <property name="dist" location="dist"/>
      
          <target name="init">
              <tstamp>
                  <format property="TIMESTAMP" pattern="yyyy-MM-dd HH:mm:ss" />
              </tstamp>
              <mkdir dir="${build}"/>
              <mkdir dir="${build}/META-INF"/>
          </target>
      
          <target name="compile" depends="init">
              <javac debug="on" srcdir="${src}" destdir="${build}"/>
          </target>
      
          <target name="dist" depends = "compile">        
              <mkdir dir="${dist}"/>      
              <property name="version.num" value="1.0.0"/>
              <buildnumber file="build.num"/>
              <manifest file="${build}/META-INF/MANIFEST.MF">
                  <attribute name="Built-By" value="${user.name}" />
                  <attribute name="Built-Date" value="${TIMESTAMP}" />                                
                  <attribute name="Implementation-Vendor" value="Company" />
                  <attribute name="Implementation-Title" value="PackageVersion" />
                  <attribute name="Implementation-Version" value="${version.num} (b${build.number})"/>
                  <section name="com/company/division/project/packageversion">
                      <attribute name="Sealed" value="false"/>
                  </section>          
              </manifest>     
              <jar destfile="${dist}/packageversion-${version.num}.jar" basedir="${build}" manifest="${build}/META-INF/MANIFEST.MF"/>                 
          </target>
      
          <target name="clean">
              <delete dir="${build}"/>
              <delete dir="${dist}"/>
          </target>
      
          <target name="run" depends="dist">      
              <java classname="com.company.division.project.packageversion.packageVersion">
                  <arg value="-h"/>
                  <classpath>
                      <pathelement location="${dist}/packageversion-${version.num}.jar"/>
                      <pathelement path="${java.class.path}"/>
                  </classpath>
              </java>
          </target>
      
      </project>
      

      【讨论】:

      • 在使用任何第三方库的实际应用程序中,这极有可能返回错误的清单。
      • 类名应该以大写开头。它应该是 PackageVersion 而不是 packageVersion。
      【解决方案8】:

      我通常也会使用版本文件。我将为每个 jar 创建一个文件,因为每个 jar 都可以有自己的版本。

      【讨论】:

        【解决方案9】:

        只是不要使用清单。创建一个 foo.properties.original 文件,其内容如 版本=@VERSION@

        在你正在做的同样的任务中,你可以复制一份到 copu foo.properties.original 然后

        【讨论】:

          猜你喜欢
          • 2019-01-24
          • 1970-01-01
          • 1970-01-01
          • 2017-05-27
          • 2012-01-19
          • 2016-06-14
          • 2019-10-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多