jar文件揭密_JAR

对于大多数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-PathMain-Class属性)不太令人兴奋,因为Servlet环境会拾取目录的全部内容并具有预定义的入口点。 综上所述,这些技巧使我们超越了“好吧,首先复制此目录中的所有内容……”的范式,这样做还使部署Java应用程序变得更加简单。


翻译自: https://www.ibm.com/developerworks/java/library/j-5things6/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值