20

我花了太多时间试图弄清楚这一点。这应该是最简单的事情,每个在 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();

有任何想法吗?

4

9 回答 9

11

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

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

@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);
    }
}
于 2010-06-18T16:55:19.920 回答
2

ClassLoader.getResource(String) will load the first manifest it finds on the classpath, which may be the manifest for some other JAR file. Thus, you can either enumerate all the manifests to find the one you want or use some other mechanism, such as a properties file with a unique name.

于 2009-03-05T16:06:04.760 回答
2

你想用这个:

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

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

于 2008-09-18T16:44:23.460 回答
1

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

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

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

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

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

于 2008-09-17T16:17:40.897 回答
1

这是我发现的有效方法:

包版本.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>
于 2008-09-29T16:28:17.507 回答
1

我发现 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());

我改编自链接文本

于 2010-06-18T11:30:49.177 回答
0

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

于 2008-09-17T16:11:22.767 回答
0

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

在同样的任务中,您可以对 copu foo.properties.original 进行复制,然后

于 2008-09-17T15:38:23.690 回答
0

您可以使用jcabi-manifestsManifests中的实用程序类,它可以自动查找和解析类路径中的所有可用文件。然后,您使用单行读取任何属性: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

于 2012-12-30T09:02:05.250 回答