John Jiang

a cup of Java, cheers!
https://github.com/johnshajiang/blog

   :: 首页 ::  :: 联系 :: 聚合  :: 管理 ::
  131 随笔 :: 1 文章 :: 530 评论 :: 0 Trackbacks
你所不知道的五件事情--JAR文件
这是Ted Neward在IBM developerWorks5 things系列文章中的一篇,讲述了关于JAR的一些应用窍门,值得大家学习。(2010.06.27最后更新)

摘要:许多Java开发者从没有深入思考过JAR--他们只是在将类传到产品服务器之前使用JAR打包这些文件罢了。但JAR并不仅仅是一个被重命名的 ZIP文件。学习如何使用Java归档文件的全部能力,包括打包Spring依赖和配置文件的小窍门。

    对大多数Java开发者而言,JAR与它的兄弟们,WAR和EAR,都是一长串Ant或Maven处理后的最终结果。一个标准的过程是将JAR复制到服务器的适应位置(或者,更少见地,复制到用户的机器上),然后就把它遗忘了。
    准确地说,JAR能做的远不止存储源代码,但你必须要知道它能做的其它事情,以及怎样去使用它。在本"5 things"系列的分期文章中所介绍的窍门将展示如何制作大部分的Java归档文件(在有些例子中,也会涉及WAR和EAR),特别是在开发时期。
    因为有众多的Java开发者在使用Spring(也因为Spring框架展示了一些相对于我们对JAR传统应用的挑战),其中若干窍门是特别针对Spring应用中的JAR文件。
    我将以一个标准的Java归档文件处理的例子开始,该例是下面各窍门的基础。

置于JAR中
    一般地,在编译源代码之后你会制作一个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++这样的编程语言,在历史上有操作系统友好方面优势,只需在命令行中引入它们的名字(helloWorld.exe)或在GUI Shell中双击它们的图标就会启动这些应用。然而在Java编程中,启动器程序--java--引导JVM运行,而后我们必须传入一个命令行参数 (com.tedneward.Hello)用于指定将要启动的含有main()方法的类。
    这些额外的步骤使得很难为Java创建用户友好的应用。不仅仅是由于最终用户必须在命令行中键入所有的这些元素(很多用户都想避免这种情况),而且他或她会由于某种原因打错字并得到一个晦涩的错误返回。
    解决方案就是使JAR文件"可执行",以便在执行JAR文件时,能让Java启动器自动地知道启动哪个类。我们所需要做的只是在JAR文件的manifest(JAR文件META-INF中的MENIFEST.MF文件)中引入一个属性,例如:

清单2. 显示入口点
Main-Class: com.tedneward.jars.Hello

manifest就是一组名值对。因为mainfest有时候对回车和空格比较敏感,所以在制作JAR时使用Ant去生成该文件要方便些。在清单3中,我就在Ant的jar任务中使用了manifest元素去指定要生成的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)容器为我们处理了许多细节,但仍有一点儿障碍:

清单4. Hello, Spring world!

package com.tedneward.jars;

import org.springframework.context.*;
import org.springframework.context.support.*;

public class Hello
{
    
public static void main(String[] args)
    {
        ApplicationContext appContext 
=
            
new FileSystemXmlApplicationContext("./app.xml");
        ISpeak speaker 
= (ISpeak) appContext.getBean("speaker");
        System.out.println(speaker.sayHello());
    }
}

因为启动器的-jar选项会被命令行中的-classpath选项所覆盖,那么当你运行上述程序时,Spring需要出现在CLASSPATH中,并且要在环境变量中。幸运地是,JAR允许针对其它JAR依赖的声明出现在manfiest中,这就隐式地创建了CLASSPATH而不需要你去声明,如清单5 所示:

清单5. Hello, 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.springframework.context-3.0.1.RELEASE-A.jar
                        ./lib/org.springframework.core-3.0.1.RELEASE-A.jar
                        ./lib/org.springframework.asm-3.0.1.RELEASE-A.jar
                        ./lib/org.springframework.beans-3.0.1.RELEASE-A.jar
                        ./lib/org.springframework.expression-3.0.1.RELEASE-A.jar
                        ./lib/commons-logging-1.0.4.jar"
 />
            
</manifest>
    
</jar>
</target>

注意到Class-Path属性包含有该应用的依赖相对于JAR的引用路径。你也可以写绝对引用路径,或者完全不需要前缀,在这种情况下就要假设这些依赖 JAR文件与应用程序的JAR文件在同一目录下。
    不幸地是,Ant的Class-Path属性对应的value属性必须出现在一行中,因为JAR manfiest无法应对多个Class-Path属性,所以,所有的依赖必须出现在manifest文件的同一行中。可以肯定的是,这种做法很丑陋,但为了能使用命令java -jar outapp.jar,这是值得的。

3. 可隐式地引用JAR
如果你有多个不同的命令行工具(或其它的应用)需要使用Spring框架的JAR文件,那么将这些Spring JAR文件置于公共路径中,以便所有的工具类都能被引用到。这样做就能避免文件系统中满是JAR文件的多份拷贝。Java运行时环境的公共JAR文件路径,即大家所知的"扩展目录",默认是位于JRE安装路径下的lib/ext子目录中。
JRE是一个可定制的路径,但仍然很少在一个给定的Java环境中定制该路径使我们能够安全放心地假设lib/ext是一个存放JAR文件的安全地方,该目录中的JAR文件将默认出现在Java运行时环境的CLASSPATH中。

4. Java 6允许类路通配符

    作为一种避免庞大CLASSPATH环境变量(Java开发者在多年前就已经抛弃它了)和/或命令行-classpath参数的努力,Java 6引入了类路径通配符选项。与在启动时必须在一个参数中显示地列出每个JAR文件不同,类路径通配符允许你通过lib/*来指定该目录下的所有JAR文件 (但不允许递归其子目录中的JAR文件)设置到类路径中。
    不幸地是,类路径通配符并不能支持之前讨论过的Class-Path属性manifest条目。为了某些开发者任务,例如代码生成或分析工具,使用类路径通配符可以更方便地启动Java应用程序(包括服务器)。

5. JAR不只是包含代码

    就像Java生态系统中的许多组成部分那样,Spring依赖一个配置文件,该文件描述了如何去构建运行环境。如前所述,Spring依赖app.xml 文件,该文件与JAR文件存在于同一个目录下--但经常地,开发者们会忘记复制JAR文件边上的配置文件。
    sysadmin会编辑某些配置文件,但也有大量的配置文件(如Hibernate映射文件)在sysadmin的域之外,这将导致发布错误。一种明智的解决方案就是将代码与配置文件打包在一起--这是可行的,因为JAR本质上就是改头换面的ZIP。只需将配置文件包含在Ant任务中,或使用jar命令去构建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文件中,可以让类的ClassLoader将该文件作为 JAR文件中的"资源"进行查找,使用ClassLoader的getResourceAsStream()方法,如清单7所示:

清单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类),然后就可以任何符合你喜好的方式来使用它们了。

结论
    本文涵盖了关于JAR的多数Java开发者最不知道的5件事情--至少基于历史和轶事证据可以这么认为。注意,所有这些与JAR相关的窍门也同样适用于 WAR。有些窍门(特别是Class-Path和Main-Class属性)对于WAR不完全正确,因为Servlet环境会获取目录中的全部内容并有一个预定义的入口点。但综合来看,这些窍门还是让我们超越了这样一种范式:"好吧,让我们开始复制目录下的所有文件吧..."。除了这些,他们还使得部署 Java应用变得非常容易。
    本系列的下一往篇文章是:你所不知道的五件事情--Java应用的性能监控。

posted on 2010-06-27 18:01 John Jiang 阅读(3106) 评论(8)  编辑  收藏 所属分类: JavaSEJava翻译

评论

# re: 你所不知道的五件事情--JAR文件(译) 2010-06-27 19:05 何杨
不错!  回复  更多评论
  

# re: 你所不知道的五件事情--JAR文件(译)[未登录] 2010-06-27 20:32 feenn
文章很好,JAR不只是包含代码——其实还可以包含动态链接库(比如SWT)、压缩包甚至是jar本身  回复  更多评论
  

# re: 你所不知道的五件事情--JAR文件(译) 2010-06-28 09:43 隔叶黄莺
还好,我还都知道这五件事情。  回复  更多评论
  

# re: 你所不知道的五件事情--JAR文件(译) 2010-06-28 10:35 Sha Jiang
> 还好,我还都知道这五件事情
嗯,这几点东西还算是常识。  回复  更多评论
  

# re: 你所不知道的五件事情--JAR文件(译) 2010-06-29 21:37 balckbat
jar 里的配置文件可写入吗?  回复  更多评论
  

# re: 你所不知道的五件事情--JAR文件(译) 2010-06-29 22:46 Sha Jiang
> jar 里的配置文件可写入吗?
可以试试java.util.jar中的API...我没有试过  回复  更多评论
  

# re: 你所不知道的五件事情--JAR文件(译) 2010-07-08 13:59 上鬼子当了
我以为人人都知道呢?原来好多人都不知道啊。惊叹!
怪不得很多程序员抱怨自己工资低。。。  回复  更多评论
  

# re: 你所不知道的五件事情--JAR文件(译) 2010-07-08 19:12 Sha Jiang
> 我以为人人都知道呢?原来好多人都不知道啊。惊叹!
也不必惊叹...在现实世界中,任何常识,总会有人不知道

> 怪不得很多程序员抱怨自己工资低。。。
。。。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: