关于IDEA中classpath的定义

Posted by     "Eric" on Wednesday, May 13, 2020

1. ClassPath到底在哪儿?

之前一直有个误解,认为classpath是编译前概念,所以看到src文件夹下有main,有test,有resources等子目录,到底哪个是classpath就很懵。后来发现,其实classpath是.java文件被编译成.class字节码文件后,运行时的一个概念。

打开IDEA中的Project Structure

结构的右边列出了4个项目中的文件类型:

  • Source Folders:表示的都是代码源文件目录,生成的class文件会输出到target->classess文件夹中,但是里面的源文件不会复制到target->classes文件夹中
  • Test Source Folders: 表示的都是测试代码源文件目录,生成的class文件同样会输出到target->classess文件夹中,并且里面的源文件不会复制到target->classes文件夹中
  • Resource Folders: 表示的都是资源文件目录,这些目录里面的文件会在代码编译运行被直接复制到target->classess文件夹中
  • Excluded Folders:表示的是target文件夹生成的位置,target是IDEA编译后的一些class信息存放地,里面有子目录target->classes来存储编译后的字节码。

除了Excluded Folders,其他三个文件夹(src\main\java src\test\java src\main\resources)都会在编译后映射到classpath

2. this.getClass().getResource()还是this.getClass().getClassLoaderI().getResource()?

在加载资源时候,上述两个指令是我们经常用到的,这两个函数都是帮我们把相对路径转换成绝对路径,保证资源的加载与运行环境的独立,但是二者的使用上还是会有些细微的差别,让我们直接看源码

// Class类中的getResource方法
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);
}

Class类中的getResource方法会先对传入的String参数进行一定的处理,然后得到该类的类加载器,最后调用该类的类加载器的getResource()方法,所以Class类中的getResource方法是ClassLoader类中getResource方法的一种封装,差别就是在resolveName这个函数上。

private String resolveName(String name) {
    if (name == null) {
        return name;
    }
    if (!name.startsWith("/")) {
        Class<?> c = this;
        while (c.isArray()) {
            c = c.getComponentType();
        }
        // 得到全限定名
        String baseName = c.getName();
        // 得到最后一个.的index
        int index = baseName.lastIndexOf('.');
        if (index != -1) {
            // 将.替换为/ 得到类所在的文件夹名,然后与name进行拼接
            name = baseName.substring(0, index).replace('.', '/')
                +"/"+name;
        }
    } else {
        name = name.substring(1);
    }
    return name;
}

如果name是“/”开头的,那么将其省略,然后返回这个字符串;如果name不是以"/“开头,那么先得到该类所在的文件夹名,然后与name进行拼接,返回这个字符串。举例,如果我们传入的是”"(空字符串),那么经过resolveName处理,返回的就是该类所在包名(例如 io.github.ericwu0930);如果传入的是"/",那么经过resolveName处理,返回的是""(空字符串)

结合类加载器中的相关知识,对于Application ClassLoader这一类加载器,因为Application ClassLoader是从classpath中寻找所需要加载的类,换句话说,classpath是根目录。因此,如果传入的字符串以"/“开头,那么就是从classpath目录下寻找资源,如果传入的字符串不以"“开头,那么就是从该类所在包下寻找资源。

3JTeuF.png

通过一个实验可以更清楚些。

package io.github.ericwu0930;

/**
 * @author erichwu
 * @date 2020/6/6
 */
public class Test {
    public void getResource(){
        System.out.println(this.getClass().getResource("/"));                // 情况1
        System.out.println(this.getClass().getResource(""));                 // 情况2
        System.out.println(this.getClass().getClassLoader().getResource("/")); // 情况3
        System.out.println(this.getClass().getClassLoader().getResource(""));  // 情况4
    }
    public static void main(String[] args) {
        Test test=new Test();
        test.getResource();
    }
}
file:/Users/eric/javaProjects/getResourceTest/out/production/getResourceTest/
file:/Users/eric/javaProjects/getResourceTest/out/production/getResourceTest/io/github/ericwu0930/
null
file:/Users/eric/javaProjects/getResourceTest/out/production/getResourceTest/

经过我们上面的分析,情况1和情况4是等价的,答案也是如此;情况3这种调用方式是错误的。