在生产中的罐子进行测试和运行时,如何创建MANIFEST.MF?
我花了太多时间试图解决这个问题。这应该是最简单的事情,并且在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();
有任何想法吗?
解决方案
回答
只是不要使用清单。创建一个foo.properties.original文件,其内容如下
版本= @ VERSION @
在执行jar的同一任务中,我们可以将其复制到copu foo.properties.original,然后
回答
我通常也会使用版本文件。我将为每个jar创建一个文件,因为每个jar都有自己的版本。
回答
如果我们使用与加载类相同的类加载器,则可以访问jar中的清单(或者任何其他)文件。
this.getClass().getClassLoader().getResourceAsStream( ... ) ;
如果我们是多线程的,请使用以下命令:
Thread.currentThread().getContextClassLoader().getResourceAsStream( ... ) ;
这对于在jar中包含默认配置文件也是一种非常有用的技术。
回答
我们要使用此:
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");
我们可以解析URL来找出清单中的清单,然后通过getInputStream()读取URL来解析清单。
回答
这是我发现的有效方法:
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>
回答
ClassLoader.getResource(String)将加载在类路径上找到的第一个清单,该清单可能是其他一些JAR文件的清单。因此,我们可以枚举所有清单以找到所需清单,或者使用其他某种机制,例如具有唯一名称的属性文件。
回答
我发现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());
我根据链接文字改编而成
回答
我们可以在不解析类url的情况下获取任意jar中任意类的清单(这可能很脆弱)。只需在所需的jar中找到我们知道的资源,然后将连接投射到JarURLConnection。
如果我们希望当类未捆绑在jar中时代码能正常工作,请对返回的URL连接类型添加一个instanceof检查。未包装类层次结构中的类将返回内部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); } }