SpringBoot中读取文件的Resource的使用,提示FileNotFoundException的异常


我在使用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可能不止这一种方法。可以其他的方法,例如ResourceUtilsClassLoader等等其他的方法。

这个问题的原因

原因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。如下:
org.springframework.boot.loader.LaunchedURLClassLoader
他们使用的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,如下图:
    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:**前缀,也可以不添加的原因了。


更多精彩内容:请关注公众号:
在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值