当需要获取项目中的配置文件时,我们可以使用类加载器来获取,主要利用的就是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文件夹并写入文件。