一、概述
JAR 文件是Java 档案。当我们构建Java 应用程序时,我们可能会包含各种JAR 文件作为库。
在本教程中,我们将探讨如何从给定的类中找到JAR 文件及其完整路径。
2. 问题介绍
假设我们在运行时有一个Class
对象。我们的目标是找出该类属于哪个JAR 文件。
一个例子可以帮助我们快速理解问题。假设我们有Guava 的Ascii
类的类实例。我们想创建一个方法来找出包含Ascii
类的JAR 文件的完整路径。
我们将主要介绍两种不同的方法来获取JAR 文件的完整路径。此外,我们将讨论它们的优缺点。
为简单起见,我们将通过单元测试断言来验证结果。
接下来,让我们看看他们的行动。
3. 使用getProtectionDomain()
方法
Java 的类对象提供了getProtectionDomain()
方法来获取ProtectionDomain
对象。然后,我们可以通过ProtectionDomain
对象获取CodeSource
。CodeSource
实例将是我们正在寻找的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
形式获取路径。
我们构建一个正则表达式并使用String
的replaceAll()
方法来提取我们需要的部分: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 评论