Spring Boot读取文件操作
我在使用Spring Boot项目的时候,在pom.xml文件中引入一个<git-commit-id-plugin>插件,这个插件就会在编译项目的时候,自动在 resources文件夹下生成一个 git.properties的文件,中间包含当前项目的版本信息。就想着通过接口的方法,将这个 git.properties文件显示在界面上,从而知道当前运行的是哪个版本。
首先说一下问题,如果是这个问题接着看
代码片段如下:
@GetMapping("/version/info")
public String versionInfo() throws IOException {
ClassPathResource classPathResource = new ClassPathResource("git.properties");
StringBuilder result = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(classPathResource.getInputStream(), StandardCharsets.UTF_8))){
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
}
return result.toString();
}
- 首先我使用Spring Boot在Idea的IDE工具中运行的时候,通过接口:localhost:8080/version/onfo,可以正常的将文件的内容显示出来。
- 然后通过mavan clean package进行打包,然后生成一个JAR文件,通过java -jar运行这个JAR文件,再次通过接口进行方法,抛出一个异常FileNotFoundException。
读取这个文件git.properties可能不止这一种方法。可以其他的方法,例如ResourceUtils,ClassLoader等等其他的方法。
这个问题的原因
原因1:
Spring Boot是使用内置的Tomcat进行运行的,我们通过maven的时候只是生成了一个JAR文件,当我们在服务器上运行的时候,是要读取这个JAR文件中的内容。提示java.io.FileNotFoundException: jar:file:\F:\test-project-712a073.jar!\BOOT-INF\classes!\git.properties的问题。而我们使用Idea运行项目的时候,他其实是读取的target/classes目录下的git.properties文件。
可以尝试将File的绝对路径打印出来,查看。
原因2
Spring Boot通过JAR文件运行和通过IDEA工具运行的时候,使用的ClassLoader是不一样的。当使用ClassPathResource读取文件的时候,是使用ClassLoader进行读取文件的。针对上面的一段代码而言,
在IDEA中,我们使用的TomcatEmbeddedWebAppClassLoader加载文件的
当我们使用java -jar运行打包的的文件的时候,使用的ClassLoader是:LaunchURLClassLoader。如下:
他们使用的ClassLoader不一样,造成读取文件的时候,实现方式也不一样。
在Spring Boot中读取文件的正确做法:
1.如果我们读取resources文件夹
- 方法一:可以使用
this.getClass().getClassLoader()..getResource("resourcetest.txt")
就会自动从resources文件夹中读取这个resourcetest.txt文件的内容,例如:
URL resource = this.getClass().getClassLoader().getResource("resourcetest.txt");
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.openStream()))) {
StringBuilder result = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
System.out.println(result.toString());
}
- 方法二:可以使用Spring中的ClassPathResource类,但是不能直接使用写资源名的方法,必须提供一个类加载器,因为默认他是用
ClassUtils.getDefaultClassLoader()
方法,这个类加载器为:TomcatEmbeddedWebAppClassLoader,如下图:
正确使用ClassPathResource的姿势为:
ClassPathResource classPathResource = new ClassPathResource("resourcetest.txt", this.getClass().getClassLoader());
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(classPathResource.getInputStream()))){
StringBuilder result = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
System.out.println(result.toString());
}
注意:直接使用ClassPathResource classPathResource = new ClassPathResource("resourcetest.txt");
会提示文件找不到的错误。
2.如果读取磁盘上的文件的话,方式就有很多种,直接使用File类,或者使用ResourceUtils类,或者FileSystemResources类
例如:使用ResourceUtils读取文件
File file = ResourceUtils.getFile("file:F:/resourcetest.txt");
// 可以添加file:,也可以不添加file前缀,因为当ResourceUtils,没有解析正确的URL的时候,就会使用File读取文件内容
// File file = ResourceUtils.getFile("F:/resourcetest.txt");
//默认的FileSystemResource,是一个相对路径,相对于当前运行这个JAR文件的目录,我们可以使用绝对路径。
//FileSystemResource fileSystemResource = new FileSystemResource("resourcetest.txt");
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))){
StringBuilder result = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
System.out.println(result.toString());
}
注意:ResourceUtils中的getFile方法可以添加 file: 前缀,也可以不添加 file: 前缀,因为当ResourceUtils,没有解析正确的URL的时候,就会使用File读取文件内容。具体看下一节。
Spring中的Resource的使用
在Spring中有一个Resource的接口,Spring对文件的操作进行封装,从而简化了各种各样文件操作的问题。
有几个重要的类:
- ClassPathResource
- FileSystemResource
- ResourceUtils
其中ClassPathResource是用来读取resources文件夹中的资源文件,而FileSystemResource是读取本地磁盘上的文件的,而ResourceUtils是一个工具类。
ClassPathResource说明:
他有四个构造方法:
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, ClassLoader classLoader) {
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
当我们直接使用ClassPathResource("资源文件路径")
的时候,可以看到ClassLoader传递的参数为NULL去调用第二个方法,在第二个代码块中,我们可以看到,当classLoader为null的时候,就会使用ClassUtils.getDefaultClassLoader()
方法,这就是我为什么在上一章节中,一定要自己指定类加载器,因为ClassUtils.getDefaultClassLoader()
使用的TomcatEmbedderWebAppLoader
。
当我们得到一个ClassPathResource对象之后,我们调用getInputStream()
方法获取输入流的时候:
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
因为我们clazz方法为空,而ClassLoader不为空,就会使用ClassLoader.getResource()
f方法,在这里使用this.classLoader.getResourceAsStream(this.path)
,这样就回到了上一章节中的第一种方法加载Resource下的资源文件的方法,使用的是:this.getClass().getClassLoader().getResourceAsStream(资源文件)
,这两个是对应的,因为this.classLoader
就是this.getClass().getClassLoader()
。
ResourceUtils方法:
其实ResourceUtils中获取输入流就两个方法:
方法一:
public static File getFile(String resourceLocation) throws FileNotFoundException {
Assert.notNull(resourceLocation, "Resource location must not be null");
if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
String description = "class path resource [" + path + "]";
ClassLoader cl = ClassUtils.getDefaultClassLoader();
URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));
if (url == null) {
throw new FileNotFoundException(description +
" cannot be resolved to absolute file path because it does not exist");
}
return getFile(url, description);
}
try {
// try URL
return getFile(new URL(resourceLocation));
}
catch (MalformedURLException ex) {
// no URL -> treat as file path
// 尝试获取本地资源文件
return new File(resourceLocation);
}
}
方法二:
public static URL getURL(String resourceLocation) throws FileNotFoundException {
}
这两个方法差不多,但是不能使用这两个方法加载resources下的资源文件,(前提:使用Spring Boot的JAR包形式运行的)
因为ClassLoader cl = ClassUtils.getDefaultClassLoader();
这句话,使用的TomcatEmbedderWebappLoader,从而造成文件找不到的异常。
在异常捕获的时候,直接使用了File类进行了封装。
但是可以从这个方法中可以看到,当我们不能正确读取URL地址的时候,就会使用FIle进行加载文件。这就是为什么在上一节中可以添加**File:**前缀,也可以不添加的原因了。
更多精彩内容:请关注公众号: