详解 Java 从 classpath 读取文件

概述

本文详细介绍在 java 中如何从 classpath 读取文件,以及在开发阶段和打包成 war 或者 jar 之后读取 classpath 下的文件需要注意的问题,具体如下

  1. class 类中的 getResource 方法 和 getResourceAsStream 方法
  2. ClassLoader 类中的 getResource 方法和 getResourceAsStream 方法
  3. Spring 中的 ClassPathResource 类和 ResourceUtils 类
  4. 在本地开发或者在 war 中读取文件
  5. 在 jar 中读取文件
  6. 使用建议

class 类中的 getResource 方法 和 getResourceAsStream 方法

通过 this.getClass() 或者 TestResources.class 可以返回一个 class 对象。

getResource 方法

  • getResource(): 返回值为 URL 类型

比如通过下面的做法可以获取到 src\test\resources\imageConfig 目录下的文件列表

1
2
3
4
5
6
7
8
9
@Test
public void test_class_getResource() {
final URL url = this.getClass().getResource("/imageConfig");
final File[] configFileArr = FileUtils.listFiles(url.getPath());
Arrays.asList(configFileArr).forEach(f -> {
System.out.println(f.getName());
System.out.println(f.getPath());
});
}

getResourceAsStream 方法

  • getResourceAsStream(): 返回值为 InputStream 类型

比如文件地址为 src\test\resources\cdWebHookObj.txt,那么在单元测试中通过如下方式可以读取到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
public void test_class_getResourceAsStream() {
final InputStream inputStream = this.getClass().getResourceAsStream("/imageConfig/rabbitmq.json");
FileUtils.readFileContent(inputStream, System.out::println);
}

public static void readFileContent(InputStream inputStream, Consumer<String> stringConsumer) {
Assert.notNull(inputStream, "the inputStream object is null");
StringBuilder data = new StringBuilder();
BufferedReader bufferedReader = null;

try {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

String temp;
while((temp = bufferedReader.readLine()) != null) {
data.append(temp);
}

stringConsumer.accept(data.toString());
} catch (Exception var8) {
logger.error("readFileContent has error", var8);
} finally {
IOUtils.closeQuietly(bufferedReader);
}

}
  • 其中 this.getClass().getResourceAsStream("/imageConfig/rabbitmq.json") 中的 /imageConfig/rabbitmq.json 相当于从 classpath 根目录下读取文件

getResource 和 getResourceAsStream 的目录问题

  1. 如果以 / 开头,表示从 类路径的根目录 开始
  2. 如果不以 / 开头,表示从 类所在包的根目录 开始
  3. 在 maven 项目中如果需要把文件放到 类所在包的根目录下读取,需要在 pom 中增加额外的配置,才能读取到,具体如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<build>
<finalName>test</finalName>

<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<!--json 的配置文件会和编译后的class文件放在一起-->
<include>**/*.json</include>
</includes>
</resource>
<resource>
<!--加载配置的资源-->
<directory>src/main/resources</directory>
</resource>
</resources>

<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

ClassLoader 类中的 getResource 方法和 getResourceAsStream 方法

  • 文件或者目录前面不需要带上 /,默认就从 类路径的根目录 开始读取
  • 获取 ClassLoader 对象的方法如下
1
2
3
4
// 方法1
InputStream io = Thread.currentThread().getContextClassLoader().getResourceAsStream("test.txt");
// 方法2
InputStream io = getClass().getClassLoader().getResourceAsStream("test.txt");

getResource 方法

  • getResource(): 返回值为 URL 类型
1
2
3
4
5
6
@Test
public void test_classloader_getResource() {
final URL url = getClass().getClassLoader().getResource("imageConfig");
final File file = new File(url.getPath());
Arrays.asList(file.listFiles()).forEach(f -> System.out.println(f.getName()));
}

getResourceAsStream 方法

  • getResourceAsStream(): 返回值为 InputStream 类型
1
2
3
4
5
@Test
public void test_classloader_getResourceAsStream() {
final InputStream inputStream = getClass().getClassLoader().getResourceAsStream("imageConfig/rabbitmq.json");
FileUtils.readFileContent(inputStream, data -> System.out.println(data));
}

getResources 方法

  • getResources(): 返回值为 Enumeration<URL> 类型

并且额外提供了 getResources 方法,具体如下

1
2
3
4
5
6
7
8
9
@Test
public void test2() throws Exception {
final Enumeration<URL> enumeration = getClass().getClassLoader().getResources("imageConfig");
while (enumeration.hasMoreElements()) {
final URL url = enumeration.nextElement();
final File file = new File(url.getPath());
Arrays.asList(file.listFiles()).forEach(f -> System.out.println(f.getName()));
}
}

Spring 中的 ClassPathResource 类和 ResourceUtils 类

ClassPathResource 类

  • org.springframework.core.io.ClassPathResource

ClassPathResource 对 ClassLoader.getResourceAsStream(String) 进行了一层额外的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 @Test
public void test_ClassPathResource_getInputStream() throws Exception {
final Resource resource = new ClassPathResource("imageConfig/elasticsearch.json");
final InputStream jsonFileInputStream = resource.getInputStream();
FileUtils.readFileContent(jsonFileInputStream, System.out::println);
}

@Test
public void test_ClassPathResource_getURL() throws Exception {
final Resource resource = new ClassPathResource("imageConfig");
final URL url = resource.getURL();
final File[] configFileArr = FileUtils.listFiles(url.getPath());
Arrays.asList(configFileArr).forEach(f -> {
System.out.println(f.getName());
System.out.println(f.getPath());
});
}

ResourceUtils 类

  • org.springframework.util.ResourceUtils
  • 文件或者目录前面需要带上 classpath:
  1. 读取文件
1
2
3
4
5
@Test
public void test_ResourceUtils_file() throws Exception {
final File file = ResourceUtils.getFile("classpath:imageConfig/rabbitmq.json");
System.out.println(FileUtils.readFileContent(file, "UTF-8"));
}
  1. 读取目录
1
2
3
4
5
@Test
public void test_ResourceUtils_path() throws Exception {
final File file = ResourceUtils.getFile("classpath:imageConfig");
Arrays.asList(file.listFiles()).forEach(f -> System.out.println(f.getName()));
}

在本地开发或者在 war 中读取文件

  1. 在本地开发的时候实际上是以文件夹的形式读取文件
  2. 如果项目打包成 war 并通过 tomcat 启动项目,实际在启动的过程中 war 会自动解压,本质也是通过文件夹的形式读取文件

在 jar 中读取文件

spring boot 会将代码打包成 jar,在 jar 中读取文件一定要返回 InputStream 对象才可以,否则会出现读取不到文件的问题,具体如下

1
2
3
4
// 方法1
InputStream io = Thread.currentThread().getContextClassLoader().getResourceAsStream("test.txt");
// 方法2
InputStream io = getClass().getClassLoader().getResourceAsStream("test.txt");

因为 jar 本身就是一个文件,类似压缩文件 zip,jar 中的文件路径和文件夹中的路径是不一样的,比如:jar:file:/home/devops/oneKeySys/one-key-sys-service.jar!/BOOT-INF/classes!/imageConfig/mysql.json,因此会导致在本地开发的时候代码是没有问题的,但是到了线上环境(linux)就会出现问题。

问题

cannot be resolved to absolute file path because it does not reside in the file system: jar

1
2
java.io.FileNotFoundException: class path resource [imageConfig/mysql.json] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/home/devops/oneKeySys/one-key-sys-service.jar!/BOOT-INF/classes!/imageConfig/mysql.json
at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:217)

使用建议

  1. 在一般的 java 中使用 ClassLoader 类中的 getResource 方法和 getResourceAsStream 方法
  2. 在 spring 中使用 ClassPathResource 类中的相关方法。

参考

打赏

  • 微信

  • 支付宝