我想知道Class.getResource()和ClassLoader.getResource()之间的区别是什么?

编辑:我特别想知道文件/目录级别是否涉及缓存。如“目录列表是否缓存在类版本中?”

我认为下面这些应该做同样的事情,但它们不是:

getClass().getResource() 
getClass().getClassLoader().getResource()

我在摆弄一些报告生成代码时发现了这一点,这些代码从WEB-INF/classes/目录中的现有文件创建了一个新文件。当使用来自Class的方法时,我可以使用getClass(). getresource()找到部署时存在的文件,但当试图获取新创建的文件时,我收到了一个空对象。浏览目录会清楚地显示新文件在那里。文件名以斜杠开头,如"/myFile.txt"。

另一方面,getResource()的ClassLoader版本确实找到了生成的文件。从这个经验来看,似乎有某种目录列表的缓存正在进行。我说得对吗?如果是的话,这在哪里有记录?

来自Class.getResource()上的API文档

寻找资源 有一个名字。的规则 查找关联的资源 类实现 定义类的类装入器。 此方法委托给该对象的方法 类装入器。如果这个对象是 由引导类装入器装入, 方法委托给 ClassLoader.getSystemResource(以)。

对我来说,这就是“阶级”。getResource实际上是调用它自己的类加载器的getResource()”。这将与执行getClass(). getclassloader (). getresource()相同。但事实显然并非如此。有人能给我一些关于这件事的说明吗?


当前回答

自Java 9以来,在模块路径上运行ClassLoader#getResource时存在一个陷阱。因此,我永远不会在新代码中使用ClassLoader#getResource。

如果您的代码在一个命名模块中,并且您使用了ClassLoader#getResource,那么即使资源在同一个模块中,您的代码也可能无法检索到资源。这是非常令人惊讶的行为。

我自己经历过,对Class#getResource和ClassLoader#getResource之间的差异感到非常惊讶。然而,它完全是根据javadoc指定的行为:

此外,除了资源名称以".class"结尾的特殊情况外,当包被无条件打开时,此方法只会在命名模块的包中找到资源(即使此方法的调用者与资源在同一个模块中)。 Javadoc(重点矿山)

其他回答

另一种更有效的方法是使用@Value

@Value("classpath:sss.json")
private Resource resource;

然后你就可以这样获取文件了

File file = resource.getFile();

第一个调用相对于.class文件进行搜索,而后一个调用相对于类路径根进行搜索。

为了调试这样的问题,我打印了URL:

System.out.println( getClass().getResource(getClass().getSimpleName() + ".class") );

自Java 9以来,在模块路径上运行ClassLoader#getResource时存在一个陷阱。因此,我永远不会在新代码中使用ClassLoader#getResource。

如果您的代码在一个命名模块中,并且您使用了ClassLoader#getResource,那么即使资源在同一个模块中,您的代码也可能无法检索到资源。这是非常令人惊讶的行为。

我自己经历过,对Class#getResource和ClassLoader#getResource之间的差异感到非常惊讶。然而,它完全是根据javadoc指定的行为:

此外,除了资源名称以".class"结尾的特殊情况外,当包被无条件打开时,此方法只会在命名模块的包中找到资源(即使此方法的调用者与资源在同一个模块中)。 Javadoc(重点矿山)

我查了一下说明书

类。getResource(字符串资源) 类加载器。getResource(字符串资源)

类的getResource() -文档说明了区别:

这个方法将调用委托给它的类加载器,在对资源名进行这些更改之后:如果资源名以“/”开头,则资源名不变;否则,在将“。”转换为“/”后,包名将放在资源名之前。如果该对象是由引导加载器加载的,则调用将委托给ClassLoader.getSystemResource。

我试着从input1.txt中读取,它在我的一个包中,和试图读取它的类一起。

以下工作:

String fileName = FileTransferClient.class.getResource("input1.txt").getPath();

System.out.println(fileName);

BufferedReader bufferedTextIn = new BufferedReader(new FileReader(fileName));

最重要的部分是调用getPath(),如果您想要字符串格式的正确路径名。不要使用toString(),因为它会添加一些额外的格式文本,这将完全混乱的文件名(你可以尝试一下,看看打印出来)。

花了2个小时调试这个…:(