我想知道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()相同。但事实显然并非如此。有人能给我一些关于这件事的说明吗?


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

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

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

我查了一下说明书

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

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

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


类。getResource可以接受一个“相对”资源名,它相对于类的包进行处理。或者,您可以使用前导斜杠指定“绝对”资源名。类加载器资源路径总是被认为是绝对的。

所以下面这些基本是等价的:

foo.bar.Baz.class.getResource("xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("foo/bar/xyz.txt");

这些也是(但它们与上面的不同):

foo.bar.Baz.class.getResource("/data/xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("data/xyz.txt");

回答是否存在缓存的问题。

我通过运行一个独立的Java应用程序进一步研究了这一点,该应用程序使用getResourceAsStream ClassLoader方法不断地从磁盘加载文件。我能够编辑该文件,并且更改立即反映出来,即,该文件在没有缓存的情况下从磁盘重新加载。

然而: 我正在做一个项目,其中有几个maven模块和相互依赖的web项目。我使用IntelliJ作为我的IDE来编译和运行web项目。

我注意到,上述似乎不再成立,原因是,我正在加载的文件现在被烘焙到一个罐子,并部署到依赖的web项目。我只是在尝试更改目标文件夹中的文件后才注意到这一点,但无济于事。这使它看起来好像正在进行缓存。


这里所有这些答案,以及这个问题的答案,都建议加载绝对url,比如“/foo/bar”。class.getResourceAsStream(String)和class.getClassLoader(). getresourceasstream (String)处理相同。但事实并非如此,至少在我的Tomcat配置/版本(当前为7.0.40)中不是这样。

MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!

抱歉,我绝对没有令人满意的解释,但我猜tomcat对类加载器做了肮脏的把戏和他的黑魔法,导致了差异。我总是使用class.getResourceAsStream(String)在过去,没有任何问题。

PS:我也把这个贴在这里了


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

以下工作:

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

System.out.println(fileName);

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

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

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


类。getResources将通过加载对象的类加载器检索资源。而类加载器。getResource将使用指定的类加载器检索资源。


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

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

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

File file = resource.getFile();


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

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

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

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