对于大多数Java开发人员而言,JAR文件及其专门的表亲(WAR和EAR)只是漫长的Ant或Maven过程的最终结果。 标准过程是将JAR复制到服务器上的正确位置(或更罕见的是,用户的计算机上),然后将其遗忘。
实际上,JAR可以做的不仅仅是存储源代码,但是您必须知道还有什么可能,以及如何要求它。 这5件事系列的本期中的技巧将向您展示如何充分利用Java Archive文件(在某些情况下还包括WAR和EAR),尤其是在部署时。
由于有如此多的Java开发人员使用Spring(并且Spring框架对我们传统的JAR使用提出了一些特殊的挑战),因此一些技巧专门针对Spring应用程序中的JAR。
我将以一个标准Java Archive文件过程的快速示例开始,它将作为后续技巧的基础。
把它放在一个罐子里
通常,在编译代码源之后,您将构建一个JAR文件,然后通过jar
命令行实用程序或更常见的是Ant jar
任务将Java代码(已按包分离)收集到一个集合中。 这个过程非常简单,我将不在这里演示,尽管在本文的后面,我们将回到如何构造JAR的主题。 现在,我们只需要存档Hello
,它是一个独立的控制台实用程序,它执行了将消息打印到控制台的非常有用的任务,如清单1所示:
清单1.归档控制台实用程序
package com.tedneward.jars;
public class Hello {
public static void main(String[] args) {
System.out.println("Howdy!");
}
}
Hello
实用程序虽然不多,但从执行代码开始,它是探索JAR文件的有用支架。
1. JAR是可执行的
NET和C ++之类的语言在历史上一直具有与OS友好的优势,因为只需在命令行中引用它们的名称( helloWorld.exe
)或在GUI Shell中双击其图标即可启动该应用程序。 但是,在Java编程中,启动器应用程序( java
)将JVM引导到进程中,并且我们必须传递一个命令行参数( com.tedneward.Hello
),该参数指示要启动其main()
方法的类。
这些额外的步骤使用Java创建用户友好的应用程序变得更加困难。 最终用户不仅必须在命令行上键入所有这些元素,许多最终用户都希望避免,但是他或她很有可能会用某种方式加粗手指并获得一个晦涩的错误。
解决方案是使JAR文件“可执行”,以便Java启动程序在执行JAR文件时将自动知道要启动哪个类。 我们要做的就是在JAR文件的清单(JAR的META-INF
子目录中的MANIFEST.MF
中引入一个条目,如下所示:
清单2.向我展示入口点!
Main-Class: com.tedneward.jars.Hello
清单只是一组名称/值对。 由于清单有时可能对回车和空格不敏感,因此在构建JAR时最容易使用Ant生成清单。 在清单3中,我使用了Ant jar
任务的manifest
元素来指定清单:
清单3.为我构建入口点!
<target name="jar" depends="build">
<jar destfile="outapp.jar" basedir="classes">
<manifest>
<attribute name="Main-Class" value="com.tedneward.jars.Hello" />
</manifest>
</jar>
</target>
现在,用户执行JAR文件所需要做的就是通过java -jar outapp.jar
在命令行上指定其文件名。 对于某些GUI Shell,双击JAR文件同样有效。
2. JAR可以包含依赖项信息
似乎Hello
实用程序一词广为流传,因此出现了更改实现的需求。 像Spring或Guice这样的依赖注入(DI)容器为我们处理了许多细节,但是仍然很麻烦:修改代码以包括使用DI容器可以导致结果,如清单4所示:
清单4.您好,Spring世界!
package com.tedneward.jars;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Hello {
public static void main(String... args) {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(AppConfig.class);
Speaker speaker = context.getBean("speaker", Speaker.class);
System.out.println(speaker.sayHello());
}
}
因为启动器的-jar
选项会覆盖-classpath
命令行选项中发生的所有情况,所以运行此代码时,Spring需要位于CLASSPATH
和环境变量中。 幸运的是,JAR允许在清单中显示其他JAR依赖项的声明,从而无需您进行声明即可隐式创建CLASSPATH,如清单5所示:
清单5.您好,Spring CLASSPATH!
<target name="jar" depends="build">
<jar destfile="outapp.jar" basedir="classes">
<manifest>
<attribute name="Main-Class" value="com.tedneward.jars.Hello" />
<attribute name="Class-Path"
value="./lib/org.spring-aop-5.0.0.BUILD-SNAPSHOT.jar
./lib/spring-beans-5.0.0.BUILD-SNAPSHOT.jar
./lib/spring-context-5.0.0.BUILD-SNAPSHOT.jar
./lib/spring-core-5.0.0.BUILD-SNAPSHOT.jar
./lib/spring-expression-5.0.0.BUILD-SNAPSHOT.jar
./lib/commons-logging-1.2.jar" />
</manifest>
</jar>
</target>
注意, Class-Path
属性包含对应用程序依赖的JAR文件的相对引用。 您也可以将其写为绝对引用或完全不带前缀,在这种情况下,将假定JAR文件与应用程序JAR位于同一目录中。
不幸的是,Ant Class-Path
属性的value
属性必须显示在一行中,因为JAR清单不能解决多个Class-Path
属性的想法。 因此,所有这些依赖项必须出现在清单文件中的一行上。 当然,这很丑陋,但是能够对java -jar outapp.jar
进行java -jar outapp.jar
值得!
3. JAR可以被隐式引用
如果您有几个使用Spring框架的不同命令行实用程序(或其他应用程序),则将Spring JAR文件放在所有实用程序都可以引用的公共位置可能会更容易。 这样做可以避免在整个文件系统中弹出多个JAR副本。 默认情况下,Java运行时用于JAR的公共位置称为“扩展目录”,位于安装的JRE位置下方的lib/ext
子目录中。
JRE是一个可自定义的位置,但是在给定的Java环境中很少对其进行自定义,因此完全可以假设lib/ext
是存储JAR的安全位置,并且可以在Java环境的CLASSPATH
上隐式使用它们。
4.允许使用类路径通配符
为了避免巨大的CLASSPATH
环境变量(Java开发人员应该在几年前-classpath
)和/或命令行-classpath
参数,Java 6引入了classpath通配符的概念。 使用classpath通配符,您不必在参数上显式列出每个JAR文件,而只需在类路径中指定lib/*
,以及该目录中列出的所有JAR文件(而不是递归地)。
不幸的是,对于前面讨论的Class-Path
属性清单条目,classpath通配符不成立。 但这确实使启动用于开发人员任务的Java应用程序(包括服务器)变得更加容易,例如代码生成工具或分析工具。
5. JAR持有的不只是代码
Java生态系统的许多部分都依赖于描述如何建立环境的配置文件,对于开发人员来说,忘记将配置文件与JAR文件一起复制是完全常见的。
某些配置文件可由sysadmin编辑,但是其中很大一部分不在sysadmin的域之内,这会导致部署错误。 明智的解决方案是将配置文件与代码打包在一起,并且这是可行的,因为JAR基本上是变相的ZIP。 构建JAR时,只需在Ant任务或jar
命令行中包含配置文件即可。
JAR还可以包括其他类型的文件,而不仅仅是配置文件。 例如,如果我的SpeakEnglish
组件想要访问属性文件,则可以像清单6那样进行设置:
清单6.随机响应
package com.tedneward.jars;
import java.util.*;
public class SpeakEnglish implements ISpeak {
Properties responses = new Properties();
Random random = new Random();
public String sayHello() {
// Pick a response at random
int which = random.nextInt(5);
return responses.getProperty("response." + which);
}
}
将responses.properties
放入JAR文件意味着可以减少与JAR文件一起部署的文件。 为此,只需要在JAR步骤中包括responses.properties
文件即可。
但是,一旦将属性存储在JAR中,您可能会想知道如何找回它们。 如果所需的数据与前面的示例一样位于同一JAR文件中,则不要费力尝试找出JAR的文件位置并使用JarFile
对象将其打开。 相反,使用清单7中所示的ClassLoader getResourceAsStream()
方法,让类的ClassLoader
在JAR文件中找到它作为“资源”:
清单7. ClassLoader找到一个资源
package com.tedneward.jars;
import java.util.*;
public class SpeakEnglish implements ISpeak {
Properties responses = new Properties();
// ...
public SpeakEnglish() {
try {
ClassLoader myCL = SpeakEnglish.class.getClassLoader();
responses.load(
myCL.getResourceAsStream(
"com/tedneward/jars/responses.properties"));
} catch (Exception x) {
x.printStackTrace();
}
}
// ...
}
您可以对任何类型的资源执行此过程:配置文件,音频文件,图形文件,并为其命名。 几乎任何文件类型都可以捆绑成一个JAR,作为InputStream
获得(通过ClassLoader
),并以适合您的任何方式使用。
结论
本文介绍了大多数Java开发人员对JAR不了解的前五件事-至少基于历史和轶事证据。 请注意,所有这些与JAR相关的技巧都对WAR同样适用。 对于WAR,某些技巧(尤其是Class-Path
和Main-Class
属性)不太令人兴奋,因为Servlet环境会拾取目录的全部内容并具有预定义的入口点。 综上所述,这些技巧使我们超越了“好吧,首先复制此目录中的所有内容……”的范式,这样做还使部署Java应用程序变得更加简单。
翻译自: https://www.ibm.com/developerworks/java/library/j-5things6/index.html