java · 2023-04-30 0

Java 使用 File、ClassLoader.getResource 和 Class.getResource 定位资源及原理

一、File

有 maven 项目,pom.xml 路径为:/home/zxm/IdeaProjects/load-test/pom.xml

1.java

@Test
public void testFile() {
    File file1 = new File("src/main/resources/test.txt");
    File file2 = new File("/");
    System.out.println(file1.getAbsolutePath());
    System.out.println(file2.getAbsolutePath());
}

结果:

/home/zxm/IdeaProjects/load-test/src/main/resources/test.txt
/

2.原理

执行 file.getAbsolutePath() 时:

如果 pathname 以 “/” 开头,则为绝对路径,file.getAbsolutePath() 的值 this.path;
如果 pathname 不以 “/” 开头,则为非绝对路径,file.getAbsolutePath() 的值是,System.getProperty("user.dir") + this.path。

// FIle.java
public File(String pathname) {
    if (pathname == null) {
        throw new NullPointerException();
    }
    this.path = fs.normalize(pathname);
    this.prefixLength = fs.prefixLength(this.path);
}

public String getAbsolutePath() {
    return fs.resolve(this);
}
// UnixFileSystem.java
public int prefixLength(String pathname) {
    if (pathname.length() == 0) return 0;
    return (pathname.charAt(0) == '/') ? 1 : 0;
}

public String resolve(File f) {
    if (isAbsolute(f)) return f.getPath();
    return resolve(System.getProperty("user.dir"), f.getPath());
}

二、ClassLoader 和 Class

1.java

@Test
public void testClassLoader() {
    ClassLoader classLoader = this.getClass().getClassLoader();
    URL resource1 = classLoader.getResource("test.txt");
    URL resource2 = classLoader.getResource("com/example/Main.class");
    URL resource3 = this.getClass().getResource("/test.txt");
    URL resource4 = this.getClass().getResource("Main.class");
    System.out.println(resource1);
    System.out.println(resource2);
    System.out.println(resource3);
    System.out.println(resource4);
}

结果:

file:/home/zxm/IdeaProjects/load-test/target/classes/test.txt
file:/home/zxm/IdeaProjects/load-test/target/classes/com/example/Main.class
file:/home/zxm/IdeaProjects/load-test/target/classes/test.txt
file:/home/zxm/IdeaProjects/load-test/target/classes/com/example/Main.class

2.原理

classLoader 包含 classpath 信息,如有:file:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/resources.jarfile:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jarfile:/home/zxm/IdeaProjects/load-test/target/classes/file:/home/zxm/IdeaProjects/load-test/target/test-classes/

执行 classLoader.getResource("test.txt"),遍历 classpath,从 classpath 中查找资源,最后使用 file:/home/zxm/IdeaProjects/load-test/target/classes/ + test.txt,定位到文件,查找到资源

// ClassLoader.java
public URL getResource(String name) {
    URL url;
    if (parent != null) {
        url = parent.getResource(name);
    } else {
        url = getBootstrapResource(name);
    }
    if (url == null) {
        url = findResource(name);
    }
    return url;
}
// URLClassLoader.java
public URL findResource(final String name) {
    /*
     * The same restriction to finding classes applies to resources
     */
    URL url = AccessController.doPrivileged(
        new PrivilegedAction<URL>() {
            public URL run() {
                return ucp.findResource(name, true);
            }
        }, acc);

    return url != null ? ucp.checkURL(url) : null;
}
// URLClassPath.java
public URL findResource(String name, boolean check) {
    Loader loader;
    int[] cache = getLookupCache(name);
    for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) {
        URL url = loader.findResource(name, check);
        if (url != null) {
            return url;
        }
    }
    return null;
}
// URLClassPath$FileLoader.java
URL findResource(final String name, boolean check) {
    Resource rsc = getResource(name, check);
    if (rsc != null) {
        return rsc.getURL();
    }
    return null;
}

Resource getResource(final String name, boolean check) {
    final URL url;
    try {
        URL normalizedBase = new URL(getBaseURL(), ".");
        url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));

        if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
            // requested resource had ../..'s in path
            return null;
        }

        if (check)
            URLClassPath.check(url);

        final File file;
        if (name.indexOf("..") != -1) {
            file = (new File(dir, name.replace('/', File.separatorChar)))
                  .getCanonicalFile();
            if ( !((file.getPath()).startsWith(dir.getPath())) ) {
                /* outside of base dir */
                return null;
            }
        } else {
            file = new File(dir, name.replace('/', File.separatorChar));
        }

        if (file.exists()) {
            return new Resource() {
                public String getName() { return name; };
                public URL getURL() { return url; };
                public URL getCodeSourceURL() { return getBaseURL(); };
                public InputStream getInputStream() throws IOException
                    { return new FileInputStream(file); };
                public int getContentLength() throws IOException
                    { return (int)file.length(); };
            };
        }
    } catch (Exception e) {
        return null;
    }
    return null;
}

执行 this.getClass().getResource("/test.txt"),解析 name,若 name 以 “/” 为前缀,去除“/”前缀,然后找到加载当前类的 classload,使用 classload.getResource(“test.txt”);

执行 this.getClass().getResource("Main.class"),解析 name,name 拼接当前类的目录,即 com/example/Main.class,然后找到加载当前类的 classload,使用 classload.getResource(“com/example/Main.class”);

// Class.java
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<?> 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 = name.substring(1);
    }
    return name;
}