拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 从类中获取JAR 文件的完整路径

从类中获取JAR 文件的完整路径

白鹭 - 2022-07-21 2173 0 2

一、概述

JAR 文件是Java 档案。当我们构建Java 应用程序时,我们可能会包含各种JAR 文件作为库。

在本教程中,我们将探讨如何从给定的类中找到JAR 文件及其完整路径。

2. 问题介绍

假设我们在运行时有一个Class对象。我们的目标是找出该类属于哪个JAR 文件。

一个例子可以帮助我们快速理解问题。假设我们有Guava 的Ascii类的类实例。我们想创建一个方法来找出包含Ascii类的JAR 文件的完整路径。

我们将主要介绍两种不同的方法来获取JAR 文件的完整路径。此外,我们将讨论它们的优缺点。

为简单起见,我们将通过单元测试断言来验证结果。

接下来,让我们看看他们的行动。

3. 使用getProtectionDomain()方法

Java 的类对象提供了getProtectionDomain()方法来获取ProtectionDomain对象。然后,我们可以通过ProtectionDomain对象获取CodeSourceCodeSource实例将是我们正在寻找的JAR 文件。此外,CodeSource.getLocation()方法为我们提供了JAR 文件的URL 对象。最后,我们可以使用Paths类来获取JAR 文件的完整路径。

3.1。实现byGetProtectionDomain()方法

如果我们将上面提到的所有步骤包装在一个方法中,几行代码就可以完成这项工作:

public class JarFilePathResolver {
 String byGetProtectionDomain(Class clazz) throws URISyntaxException {
 URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
 return Paths.get(url.toURI()).toString();
 }
 }

接下来,我们以GuavaAscii类为例,测试我们的方法是否按预期工作:

String jarPath = jarFilePathResolver.byGetProtectionDomain(Ascii.class);
 assertThat(jarPath).endsWith(".jar").contains("guava");
 assertThat(new File(jarPath)).exists();

如我们所见,我们已经通过两个断言验证了返回的jarPath

  • 首先,路径应该指向Guava JAR 文件

  • 如果jarPath是有效的完整路径,我们可以从jarPath,创建一个File对象,并且该文件应该存在

如果我们运行测试,它就会通过。所以byGetProtectionDomain()方法按预期工作。

3.2.getProtectionDomain()方法的一些限制

如上面的代码所示,我们的byGetProtectionDomain()方法非常紧凑和简单。但是,如果我们阅读getProtectionDomain()方法的JavaDoc,它会说**getProtectionDomain()方法可能会抛出SecurityException** 。

我们已经编写了一个单元测试,并且测试通过了。这是因为我们正在本地开发环境中测试该方法。在我们的示例中,Guava JAR 位于我们的本地Maven 存储库中。因此,没有引发SecurityException

但是,某些平台,例如Java/OpenWebStart 和某些应用服务器,可能会通过调用getProtectionDomain()方法来禁止获取ProtectionDomain对象。因此,如果我们将应用程序部署到这些平台,我们的方法将失败并抛出SecurityException.

接下来,让我们看看另一种获取JAR 文件完整路径的方法。

4. 使用getResource()方法

我们知道我们调用Class.getResource()方法来获取类的资源的URL对象。那么我们就从这个方法入手,最终解析出对应JAR文件的全路径。

4.1。实现byGetResource()方法

让我们先看一下实现,然后了解它是如何工作的:

String byGetResource(Class clazz) {
 URL classResource = clazz.getResource(clazz.getSimpleName() + ".class");
 if (classResource == null) {
 throw new RuntimeException("class resource is null");
 }
 String url = classResource.toString();
 if (url.startsWith("jar:file:")) {
 // extract 'file:......jarName.jar' part from the url string
 String path = url.replaceAll("^jar:(file:.*[.]jar)!/.*", "$1");
 try {
 return Paths.get(new URL(path).toURI()).toString();
 } catch (Exception e) {
 throw new RuntimeException("Invalid Jar File URL String");
 }
 }
 throw new RuntimeException("Invalid Jar File URL String");
 }

byGetProtectionDomain方法相比,上面的方法看起来很复杂。但实际上,它也很容易理解。

接下来,让我们快速浏览一下该方法并了解其工作原理。为简单起见,我们针对各种异常情况抛出RuntimeException

4.2.了解它是如何工作的

首先,我们调用Class.getResource(className)方法来获取给定类的URL。

如果该类来自本地文件系统上的JAR 文件,则URL 字符串应采用以下格式

jar:file:/FULL/PATH/TO/jarName.jar!/PACKAGE/HIERARCHY/TO/CLASS/className.class

例如,下面是Linux 系统上Guava 的Ascii类的URL 字符串:

jar:file:/home/kent/.m2/repository/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar!/com/google/common/base/Ascii.class

正如我们所见,JAR 文件的完整路径位于URL 字符串的中间。

由于不同操作系统上的文件URL 格式可能不同,我们将提取“ file:…..jar”部分,将其转换回URL对象,并使用Paths类以String形式获取路径。

我们构建一个正则表达式并使用StringreplaceAll()方法来提取我们需要的部分:String path = url.replaceAll(“^jar:(file:.*[.]jar)!/.*”, “$1”);

接下来,类似于byGetProtectionDomain()方法,我们使用Paths类获得最终结果。

现在,让我们创建一个测试来验证我们的方法是否适用于Guava 的Ascii类:

String jarPath = jarFilePathResolver.byGetResource(Ascii.class);
 assertThat(jarPath).endsWith(".jar").contains("guava");
 assertThat(new File(jarPath)).exists();

如果我们试一试,测试就会通过。

5.结合两种方法

到目前为止,我们已经看到了两种解决问题的方法。byGetProtectionDomain方法简单可靠,但由于安全限制,在某些平台上可能会失败。

另一方面,byGetResource方法没有安全问题。但是,我们需要做更多的手动操作,例如处理不同的异常情况以及使用正则表达式提取JAR 文件的URL 字符串。

5.1。实现getJarFilePath()方法

我们可以将这两种方法结合起来。首先,让我们尝试使用byGetProtectionDomain()解析JAR 文件的路径。如果失败,我们调用byGetResource()方法作为备用方法:

String getJarFilePath(Class clazz) {
 try {
 return byGetProtectionDomain(clazz);
 } catch (Exception e) {
 // cannot get jar file path using byGetProtectionDomain
 // Exception handling omitted
 }
 return byGetResource(clazz);
 }

5.2.测试getJarFilePath()方法

为了模拟byGetProtectionDomain()在我们的本地开发环境中抛出SecurityException,让我们添加Mockito 依赖项并使用@Spy注释部分模拟JarFilePathResolver

@ExtendWith(MockitoExtension.class)
 class JarFilePathResolverUnitTest {
 @Spy
 JarFilePathResolver jarFilePathResolver;
 ...
 }

接下来,我们先测试一下getProtectionDomain()方法没有抛出SecurityException的场景:

String jarPath = jarFilePathResolver.getJarFilePath(Ascii.class);
 assertThat(jarPath).endsWith(".jar").contains("guava");
 assertThat(new File(jarPath)).exists();
 verify(jarFilePathResolver, times(1)).byGetProtectionDomain(Ascii.class);
 verify(jarFilePathResolver, never()).byGetResource(Ascii.class);

如上代码所示,除了测试路径是否有效外,我们还验证了如果我们可以通过byGetProtectionDomain()方法获取JAR 文件的路径,那么永远不应该调用byGetResource()方法。

当然,如果byGetProtectionDomain()抛出SecurityException,这两个方法将被调用一次:

when(jarFilePathResolver.byGetProtectionDomain(Ascii.class)).thenThrow(new SecurityException("not allowed"));
 String jarPath = jarFilePathResolver.getJarFilePath(Ascii.class);
 assertThat(jarPath).endsWith(".jar").contains("guava");
 assertThat(new File(jarPath)).exists();
 verify(jarFilePathResolver, times(1)).byGetProtectionDomain(Ascii.class);
 verify(jarFilePathResolver, times(1)).byGetResource(Ascii.class);

如果我们执行测试,两个测试都会通过。

六,结论

在本文中,我们学习了如何从给定的类中获取JAR 文件的完整路径。


标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *