jar文件揭密

    技术2024-02-01  91

    对于大多数Java开发人员而言,JAR文件及其专门的表亲(WAR和EAR)只是漫长的Ant或Maven过程的最终结果。 标准过程是将JAR复制到服务器上的正确位置(或更罕见的是,用户的计算机上),然后将其遗忘。

    实际上,JAR可以做的不仅仅是存储源代码,但是您必须知道还有什么可能,以及如何要求它。 这5件事系列的本期中的技巧将向您展示如何充分利用Java Archive文件(在某些情况下还包括WAR和EAR),尤其是在部署时。

    由于有如此多的Java开发人员使用Spring(并且Spring框架对我们传统的JAR使用提出了一些特殊的挑战),因此一些技巧专门针对Spring应用程序中的JAR。

    关于本系列

    所以您认为您了解Java编程吗? 事实是,大多数开发人员从头开始学习Java平台,仅学习足够的知识即可完成工作。 在本系列中,Ted Neward深入挖掘了Java平台的核心功能,以发现鲜为人知的事实,这些事实可以帮助您解决最棘手的编程难题。

    我将以一个标准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()); } }

    关于春天的更多信息

    本技巧假定您熟悉依赖注入和Spring框架。

    因为启动器的-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

    相关资源:微信小程序源码-合集6.rar
    Processed: 0.011, SQL: 9