首先会去父类加载器找,直到父类为 null,就是 Bootstrap 加载器。如果整个过程都没有找到资源,则通过自己定义的 findResource 方法去找。对于 AppClassLoader,由于是 URLClassLoader 的子类,因此可以根据 URL 找到资源。
注意:传入的 name 不能以 / 前导,否则找不到资源。使用时一般传入 com/cannedbread/app/xxx.properties 之类。
首先可以看到调用了 resolveName 方法,这个方法从源码来看,有三种处理:
name 为 null方法返回 null
name 没有以 / 前导这说明开发者意图传入一个相对路径。通过当前类获取 baseName,末尾再追加传入的 name。比如我传入 param.properties,当前类为 com.cannedbread.app,那么就会去 com/cannedbread/app/param.properties 下寻找。
name 以 / 前导这说明开发者意图从根目录寻找。程序去除了 name 前面的 /,将 name 替换成相对路径,如果需要获取 com/cannedbread/app/param.properties,则必须传入 /com/cannedbread/app/param.properties。
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; }再回到 Class.getResourceAsStream,调用获取了 ClassLoader,如果为 null 则采用系统类加载器,否则采用获取的类加载器,一般多为 sun.misc.Launcher$AppClassLoader
public InputStream getResourceAsStream(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0(); if (cl == null) { // A system class. return ClassLoader.getSystemResourceAsStream(name); } return cl.getResourceAsStream(name); }下面再看看 ClassLoader.getResourceAsStream 方法,首先会查看父类加载器,一般 sun.misc.Launcher$AppClassLoader 的父类加载器是 sun.misc.Launcher$ExtClassLoader,如果没有资源就使用 Bootstrap 类加载器,所以会优先找父类加载器的资源。
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; }所有的资源都会转化为相对路径去获取,因此使用时需要注意。
