当需要获取项目中的配置文件时,我们可以使用类加载器来获取,主要利用的就是getResource
和
getResourceAsStream
方法
一、获取项目的classpath路径
以一个springboot项目举例,在idea中运行时,classpath路径指的就是idea运行时给我们创建的
target/classes
目录,我们先尝试在idea中运行并获取此目录,有两种方式,
java.lang.Class#getResource
或者java.lang.ClassLoader#getResource
先来看一个简单的示例
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/sayHello")
public String sayHello(){
System.out.println(HelloController.class.getResource("/"));
System.out.println(HelloController.class.getClassLoader().getResource(""));
return "hello";
}
}
上边这段代码中分别使用两种方法来获取了项目的classpath路径,访问这个controller控制台会输出
file:/D:/my-study/cnblog-demo-project/springboot-01/target/classes/
file:/D:/my-study/cnblog-demo-project/springboot-01/target/classes/
file:/D:/my-study/cnblog-demo-project/springboot-01/target/classes/com/lyy/controller/
我的项目结构是这样的
这个classes目录就是我在本地运行这个项目时的classpath目录。
注意使用HelloController.class.getResource("/")
获取目录时要带/,而使用ClassLoader获取时不用带/,
这是为什么呢,需要看下Class#getResource方法的源码,注意这个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);
}
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
//如果传的不是以/开头,会以当前这个类的class文件所在的目录作为起点来找文件,
//所以会得到上边输出结果中第三行的输出内容。
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,会把/截掉然后去调用类加载器的getResource方法
name = name.substring(1);
}
return name;
}
总结下来就是java.lang.Class#getResource
是通过调用类加载器的getResource方法加载资源,并且如果传的name不带/就会以当前class文件所在的路径作为起点搜索,如果传了/就会在当前项目的classpath下搜索资源。
接下来我们在看下如果把工程打包成jar包后运行输出的路径是什么样子的。
jar:file:/D:/my-study/cnblog-demo-project/springboot-01/target/springboot-01-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/
jar:file:/D:/my-study/cnblog-demo-project/springboot-01/target/springboot-01-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/
jar:file:/D:/my-study/cnblog-demo-project/springboot-01/target/springboot-01-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/com/lyy/controller/
可以看到打成jar包运行获取的路径是一个jar包中的路径。
二、获取项目中classpath下的资源文件。
同样用上边那个springboot工程举例,resources目录下的文件会被打包到classpath目录下,如果我们在idea中运行项目,最终会跑到target/classes
目录下,这时是可以使用字节流来读取文件内容的。
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/sayHello")
public String sayHello() throws IOException {
//这个getResource方法并不会校验文件是否存在
URL classpath = HelloController.class.getResource("/res1.txt");
FileInputStream inputStream = new FileInputStream(classpath.getPath());
int len =0;
byte[] buf = new byte[500];
StringBuilder sb = new StringBuilder();
while((len = inputStream.read(buf))!=-1){
sb.append(new String(buf,0,len));
}
return sb.toString();
}
}
像上边这样使用字节流来读取文件,在本地运行时可以,但当把项目打成jar后运行就会报错,因为用FileInputStream读取不到jar包内的文件。为了解决这个问题我们要用类加载器去读取文件,不使用输入流去读取文件。
@GetMapping("/sayHello")
public String sayHello() throws IOException {
//这个getResourceAsStream方法使用类加载器去读取文件
InputStream inputStream = HelloController.class.getResourceAsStream("/res1.txt");
int len =0;
byte[] buf = new byte[500];
StringBuilder sb = new StringBuilder();
while((len = inputStream.read(buf))!=-1){
sb.append(new String(buf,0,len));
}
return sb.toString();
}
三、写入文件
在idea中启动项目时可以随意的往classpath写入文件因为目录是真实存在的,如果打成jar包后启动则不能往classpath下写文件了,因为这时这个目录已经在jar包内部了。如果一定要写文件可以利用相对路径往jar包本身所在的目录中写入文件
File file = new File("settings");
if(!file.exists()){
file.mkdir();
}
FileOutputStream outputStream = new FileOutputStream("settings/output1.txt");
outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));
这样就会在jar包所在的目录中创建settings文件夹并写入文件。