情景列举
-
文件路径分为相对路径和绝对路径;
-
现实中环境分为maven+Idea集成开发环境、windows运行环境、Linux运行环境;
-
包的形式有jar包和war包;
下面研究:
-
在不同环境下,不同包形式,不同路径形式的文件读取方式。(暂时只测试了
springboot-jar
包)
-
读取方式采用:普通Java中
ClassLoader
方式 和 Spring中ResourceUtils/ResourceLoader
两种 -
基本的java中
InputStream + File
文件读取方式测试失败,下面不予赘述
# [1]相对路径
./config/hello.txt
# [2]相对路径,类似于[1]中的相对路径
config/hello.txt
# [3] 绝对路径(一般指Linux下,# win下一般会带着盘符)
/config/hello.txt
/D:/config/hello.txt
. 代表目前所在的目录
.. 代表上一层目录
/ 代表根目录
解决问题
如何读取到封装在
Jar
包里面的(在指定文件夹下面/resources/config/*
)相关配置或者证书文件,而不是去读外部系统文件或者挂载到分布式存储系统的文件(k8s)等。
// 无论何种环境,禁止使用以下代码块(不允许使用File来操作)
File file = resource.getFile();
System.out.println(file.getPath());
FileInputStream inputStream = new FileInputStream(file);
方法一:利用单纯地ClassLoader
来操作,但是对于文件路径有局限性(具体看运行环境),其中默认的ClassLoader
为TomcatEmbeddedWebappClassLoader
。
@RestController
@RequestMapping("/java")
public class JustTestJavaController {
@GetMapping("/testDefaultClassLoader")
public String testDefaultClassLoader(@RequestParam(required = false) String path) {
StringBuilder sb = new StringBuilder();
try {
if (path == null) {
// path = "config/hello.txt";
path = "./config/hello.txt";
}
System.out.println(path);
ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader();
assert defaultClassLoader != null;
URL resource = defaultClassLoader.getResource(path);
assert resource != null;
InputStream inputStream = resource.openStream();
int len;
byte[] buf = new byte[1024];
while ((len = inputStream.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
return sb.toString();
}
@GetMapping("/testDemoClassLoader")
public String testDemoClassLoader(@RequestParam(required = false) String path) {
StringBuilder sb = new StringBuilder();
try {
if (path == null) {
// path = "config/hello.txt";
path = "./config/hello.txt";
}
System.out.println(path);
ClassLoader classLoader = DemoApplication.class.getClassLoader();
URL resource = classLoader.getResource(path);
assert resource != null;
InputStream inputStream = resource.openStream();
int len;
byte[] buf = new byte[1024];
while ((len = inputStream.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
return sb.toString();
}
}
方法二:利用Spring的ResourceLoader
可以获取到,类似于mybatis读取mapper.xml
# mybatis部分配置项
mybatis.mapper-locations=classpath*:/mapper/**/*.xml
// mybatis部分源码
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
private String[] mapperLocations;
public Resource[] resolveMapperLocations() {
return Stream.of(Optional.ofNullable(this.mapperLocations).orElse(new String[0]))
.flatMap(location -> Stream.of(getResources(location))).toArray(Resource[]::new);
}
private Resource[] getResources(String location) {
try {
return resourceResolver.getResources(location);
} catch (IOException e) {
return new Resource[0];
}
}
其中ResourceLoader
构造方法中,可以传入ClassLoader
来指定类加载器,一般springboot
项目指定启动类DemoApplication.class.getClassLoader()
。若果不指定,大概率是使用TomcatEmbeddedWebappClassLoader
。所以,spring
实际上使用的依旧是ClassLoader
的方法来加载资源,只不过在path
处理上更加灵活,小部分支持路径正则匹配(Spring
的PathMatcherUtil
)
@RestController
@RequestMapping("/spring")
public class JustTestSpringController {
ResourceLoader singleLoader = new PathMatchingResourcePatternResolver(DemoApplication.class.getClassLoader());
ResourcePatternResolver batchLoader = new PathMatchingResourcePatternResolver();
@GetMapping("/testInputPathSingle")
public String testInputPathSingle(@RequestParam(required = false) String path) {
StringBuilder sb = new StringBuilder();
try {
if (path == null) {
// Thats all ok
//path = "/config/hello.txt";
//path = "config/hello.txt";
//path = "./config/hello.txt";
//path = "classpath:config/hello.txt";
//path = "classpath:./config/hello.txt";
path = "classpath:/config/hello.txt";
}
System.out.println(path);
Resource resource = singleLoader.getResource(path);
InputStream inputStream = resource.getInputStream();
int len;
byte[] buf = new byte[1024];
while ((len = inputStream.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
}
return sb.toString();
}
@GetMapping("/testInputPathBatch")
public String testInputPathBatch(@RequestParam(required = false) String path) {
StringBuilder sb = new StringBuilder();
try {
if (path == null) {
// Thats all ok
//path = "/config/hello.txt";
//path = "config/hello.txt";
//path = "./config/hello.txt";
//path = "classpath*:config/hello.txt";
//path = "classpath*:./config/hello.txt";
path = "classpath*:/config/hello.txt";
// and Support regular expressions
//path = "classpath*:/config/*.txt";
}
System.out.println(path);
Resource[] resources = batchLoader.getResources(path);
for (Resource res : resources) {
InputStream inputStream = res.getInputStream();
int len;
byte[] buf = new byte[1024];
while ((len = inputStream.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
inputStream.close();
sb.append(":");
}
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
}
return sb.toString();
}
}
环境测试
本地IDEA+Maven集成开发环境
/* 方法一 */
// path = "/config/hello.txt" 读取失败
// path = "config/hello.txt" 读取成功
// path = "./config/hello.txt" 读取成功
/* 方法二 */
// 列举的路径中,所有情况下都可以
Windows服务机
/* 方法一 */
// path = "/config/hello.txt" 读取成功
// path = "config/hello.txt" 读取成功
// path = "./config/hello.txt" 读取失败
/* 方法二 */
// 列举的路径中,所有情况下都可以
Linux服务机
/* 方法一 */
// path = "/config/hello.txt" 读取成功
// path = "config/hello.txt" 读取成功
// path = "./config/hello.txt" 读取失败
/* 方法二 */
// path = "classpath*:./config/hello.txt" 读取失败
// path = "classpath*:./config/*.txt" 读取失败
// path = classpath:./config/hello.txt 读取成功
// path = classpath:./config/*.txt 读取成功
// 其他均可