Web项目中应该经常有这样的需求,在maven项目的resources目录下放一些文件。比如一些配置文件,资源文件等。文件的读取方式有好几种方式,本文会对常用的读取方式做一个总结,并说明一下应该注意的地方。

准备工作新建一个spring-test 的maven项目,resources目录下创建测试文件conf.properties、city_code.json (json文件夹下)。添加pom依赖

使用 FileUtils、IOUtils等工具类,需要引入 commons-io jar包。

org.springframework.boot
spring-boot-starter-web
2.2.7.RELEASE
commons-io
commons-io
2.6
一、通过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;
}