Web项目中应该经常有这样的需求,在maven项目的resources目录下放一些文件。比如一些配置文件,资源文件等。文件的读取方式有好几种方式,本文会对常用的读取方式做一个总结,并说明一下应该注意的地方。
准备工作
- 新建一个spring-test 的maven项目,resources目录下创建测试文件conf.properties、city_code.json (json文件夹下)。
- 添加pom依赖
使用 FileUtils、IOUtils等工具类,需要引入 commons-io jar包。
<!-- spring web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<!-- commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
一、通过ClassLoader读取文件
ClassLoader.getResourceAsStream()获取文件输入流
public void loadPropertiesFile() throws IOException {
Properties properties = new Properties();
properties.load(this.getClass().getClassLoader().getResourceAsStream("conf.properties"));
log.info(properties.getProperty("file.max.size"));
}
ClassLoader.getResourceAsStream()获取文件的URL
public void loadJsonFile() throws IOException {
//获取文件的URL
URL url = this.getClass().getClassLoader().getResource("json/city_code.json");
String content = FileUtils.readFileToString(new File(url.getPath()), StandardCharsets.UTF_8);
log.info(content);
}
二、通过Class读取文件
Class.getResourceAsStream()获取文件的输入流
public void loadPropertiesFile() throws IOException {
Properties properties = new Properties();
properties.load(this.getClass().getResourceAsStream("/conf.properties"));
log.info(properties.getProperty("file.max.size"));
}
Class.getResourceAsStream()获取文件的URL
public void loadJsonFile() throws IOException {
//获取文件的URL
URL url = this.getClass().getResource("/json/city_code.json");
String content = FileUtils.readFileToString(new File(url.getPath()), StandardCharsets.UTF_8);
log.info(content);
}
Class.getResourceAsStream() 和 ClassLoader.getResourceAsStream()有一个显著的区别,那就是前者加载的时候需要在文件路径前加一个 "/"。
三、Spring项目读取文件
- 通过ResourceUtils.getFile()方法可以读取File
public void loadJsonFile() throws IOException {
//获取文件的URL
File file = ResourceUtils.getFile("classpath:json/city_code.json");
String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
log.info(content);
}
- 通过ClassPathResource获取输入流
ClassPathResource的构造参数path不会做绝对路径的区分,"json/city_code.json" 或者 "/json/city_code.json"都能成功获取文件的输入流。
@SneakyThrows
public void loadJsonFil1(){
ClassPathResource classPathResource = new ClassPathResource("json/city_code.json");
InputStream inputStream = classPathResource.getInputStream();
String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
log.info(content);
}
四、如何区分要不要加"/"
在使用这些方式读取文件时,都需要传入一个文件路径。至于要不要在路径前加一个"/"时常让人感到很困惑,这里稍微做一个总结。如果通过Class来读取文件,那么路径需要以"/"开始。通过ClassLoader或者Spring提供的工具类读取文件其实都不需要在路径前加"/"。
五、总结
碰到这些问题最好的解决办法,就是去翻一翻源码,然后断点调试看看一个读取文件过程中各个变量的值。通过看源码的方式不但能解决问题,还能帮自己捋清楚这些读取文件方式到底有什么联系和区别。
这里摘一段Class类两个方法的源码,通过代码可以得到如下结论。
- Class和ClassLoader都可以读取文件,不过Class读取文件还是通过ClassLoader来实现的。
- Class读取文件之前需要经过resolveName方法,resolveName会对文件路径进行解析。如果路径以"/"开始,则删除"/"。如果路径不是"/"开始,则会在文件路径前拼接包名前缀,然后用新的文件路径来读取文件。
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
/**
* Add a package name prefix if the name is not absolute Remove leading "/"
* if name is absolute
*/
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
name = name.substring(1);
}
return name;
}