Java扩展机制在Java教程中被描述为“一种标准的,可伸缩的方式,以使自定义API可供Java平台上运行的所有应用程序使用。” 如了解扩展类加载中所述 ,“扩展框架利用类加载委托机制”,其中扩展类在rt.jar (和相关的JAR)中的引导类之后,但在从典型类路径加载的类之前加载。
扩展目录的工作方式与类路径有点类似,因为扩展目录的一部分是类加载机制的一部分,扩展目录中JAR中可用的类对Java应用程序可用。 但是有一些关键的区别,下面将重点介绍其中一些。
特性 | 类路径 | 扩展机制(可选软件包) |
---|---|---|
范围 | 通常针对特定应用
主机上可能所有的JRE
| 在特定JRE中运行的所有JVM
所有主持人的JRE
|
如何指定 | .jar文件
.class文件
| 加载指定目录中的所有JAR文件(即使扩展名不是.jar 或根本没有扩展名)。 |
类加载顺序 | 引导程序和扩展加载之后。 | 在引导程序之后但在类路径之前。 |
最值得一提的最重要观察结果之一是,即使扩展名机制没有.jar
扩展名,其扩展机制仍会提取扩展名目录中的所有JAR格式文件。 这样做的含义是,虽然可以将位于类路径目录中的JAR的名称更改为.jar
以外的扩展名,以使通配符不起作用,但该技术不适用于扩展名目录。
我将在本文中使用一些简单的示例来演示其中的一些差异。 接下来的两个代码清单是一个非常简单的HelloWorld
类和一个名为Main
的主应用程序类,它使用HelloWorld
类。
HelloWorld.java
public class HelloWorld
{@Overridepublic String toString(){return "Hello, World!";}
}
Main.java
import static java.lang.System.out;public class Main
{public static void main(final String[] arguments){out.println(new HelloWorld());}
}
为了演示类路径和扩展机制( 可选软件包 )之间的主要区别,我将把已编译的HelloWorld.class
文件归档到一个名为HelloWorld.jar
的JAR中,并将其放置在与已编译的Main.class
文件不同的目录中。
为了演示传统类路径的用法,我将HelloWorld.jar
文件放置在名为C:\hello
的目录中,并将通过通配符(*)访问该JAR,以便Main
使用。 接下来的两个屏幕快照对此进行了演示。
前面的两个图像表明,即使我已将Java Main
应用程序从当前目录中删除,Java Main
应用程序仍然可以加载HelloWorld.class
文件,因为已明确告知Java启动程序 (通过-classpath
选项)在C:\hello
查找它C:\hello
使用扩展机制(可选软件包),可以加载类而无需将其放在同一目录中并且没有明确的类路径规范。 这显示在下一个屏幕快照中。
上一个屏幕快照展示了Java启动器甚至不需要在同一目录中或在其类路径中指定的HelloWorld.class
时,该类位于扩展(可选软件包)目录中的JAR中。 人们通常认为这是使用扩展机制的好处,因为使用该JRE的所有应用程序(或主机上可能的所有应用程序)都可以看到相同的类,而无需在类路径中明确指定它们。
使用指示应用程序从JAR加载类的传统类路径方法,包含.class
文件的JAR文件需要以.jar
扩展名结尾。 下一个屏幕快照演示了在同一类路径引用的目录中将HelloWorld.jar
重命名为HelloWorld.backup
时发生的情况。
最后一张图片演示了当类路径引用目录中的JAR文件没有.jar
扩展名时,遇到NoClassDefFoundError 。 可能有些令人惊讶的是,扩展(可选软件包)机制的工作方式不同。 而是,将在扩展名指定目录中加载所有JAR文件,而不管其扩展名如何,即使它们具有文件扩展名也是如此。 在下一个屏幕图像中将对此进行演示。
上一张图像演示了将扩展目录中的JAR文件重命名为没有任何文件扩展名不会阻止类加载器加载该JAR的类。 换句话说,类加载机制基于文件类型而不是文件名或扩展名来加载指定扩展目录中的所有JAR文件。 正如Optional Packages Overview总结的那样,“对于任何特定的JAR文件本身或其中包含的使其成为已安装的可选软件包的类,没有什么特别的。 由于它位于jre / lib / ext中,因此它是一个已安装的可选软件包。”
在扩展目录内的JAR中放置太多类定义会带来一些风险和弊端 。 例如,当人们看到在类路径上显式指定的类具有所涉及的方法时,为什么会发生NoSuchMethodError可能令人发狂 。 我之前已经写过 NoSuchMethodError
的许多潜在原因之一,但是扩展目录中JAR文件中遗忘的过时和过时的类定义是另一个潜在原因。 接下来演示。
接下来的两个代码清单显示Main.java
和HelloWorld.java
修订版。 特别是, HelloWorld
具有Main
新版本调用的全新方法。 在这种情况下,当我运行Main
时,我将把新编译的HelloWorld.class
文件保留在同一目录中,以证明扩展目录中JAR中的HelloWorld.class
的旧版本优先于新版本。当前目录中的热点HelloWorld.class
。
修改后的Hello World.java(新方法)
public class HelloWorld
{@Overridepublic String toString(){return "Hello, World!";}public String directedHello(final String name){return "Hello, " + name;}
}
修改后的Main.java
import static java.lang.System.out;public class Main
{public static void main(final String[] arguments){final HelloWorld helloWorld = new HelloWorld();out.println(helloWorld);out.println(helloWorld.directedHello("Dustin"));}
}
上一张图片展示了extensions目录中现在过时的HelloWorld
类定义优先于同一目录中的HelloWorld
新类定义。 即使当我在类路径上指定当前目录时,扩展目录中的旧版本也具有优先权。 这在下一个屏幕快照中显示,该快照还显示扩展目录中“隐藏”较新JAR及其类的较新方法的JAR甚至都没有以.jar
扩展名命名。
刚刚演示的示例甚至都不是在指定的扩展目录(或多个目录)中忘记JAR可能导致的最困难的情况。 在那个例子中,至少我有一个NoSuchMethodError
来提醒我一个问题。 当旧类定义具有相同的方法签名但具有过时的方法实现时,可能存在调试甚至可能更加困难的情况。 在这种情况下,可能没有任何错误,异常或可抛出,但是应用程序逻辑将无法正常工作或无法正常工作。 以前的功能可能在代码库中存在一段时间,甚至在被认为是问题之前,尤其是在缺少单元测试和其他测试的情况下。
使用extensions目录可以使开发人员更轻松,因为存在于extensions目录(或多个目录)中的JAR文件中的类可用于与extensions目录关联的JRE中的所有应用程序(如果使用操作系统,还可以与主机上的所有JRE关联)使用。基于主机的扩展目录。 但是,过于自由地使用目录存在一定的风险。 很容易忘记,该目录中JAR中的过时类定义正在阻止类加载器加载类定义的较新的,看似明显的版本。 当发生这种情况时,使开发人员的生活变得更轻松的扩展(可选软件包)机制现在变得更加困难。
Elliotte Rusty Harold提供了有关使用扩展(可选软件包)机制的警告,“虽然这看起来很方便,但这也是一个长期错误……迟早(可能不久),您将加载错误版本的类从您根本没有想过的地方开始,就浪费了很多时间进行调试。” Java教程还建议您谨慎(我强调了这一点),“由于此机制扩展了平台的核心API,因此应谨慎使用它 。 尽管它也可能适用于站点范围的接口,但最通常用于标准化接口(如Java Community Process定义的接口)。”
尽管扩展(可选软件包)机制与类路径机制相似,并且两者均用作类加载的一部分,但要注意两者之间的差异。 特别是,请务必记住,将加载位于引用为扩展目录的目录中的所有JAR文件(即使它们没有.jar
文件扩展名)。 重命名这些JAR甚至更改其文件扩展名都不足以使类加载忽略它们。 另一方面,对于classpath,重命名JAR足以防止在classpath显式指定单个JAR文件时进行加载,并且即使classpath使用通配符(*)来指定所有JAR,更改文件扩展名通常也足以防止加载目录。
在某些情况下,扩展(可选软件包)机制是适当的选择,但这些情况似乎很少见。 在处理无法解释的NoSuchMethodError
时,请记住扩展机制(可选软件包),这一点也很重要,以便人们可以检查出该违规者是否住在该目录中。
翻译自: https://www.javacodegeeks.com/2014/10/java-extension-mechanism-loads-all-jars.html