jdk版本: 17
示例代码
创建一个 Spring Boot Web
项目(只是引入了 spring-boot-starter-web
依赖 ),然后在项目的 resources
目录下创建 META-INF/spring.factories
文件,测试代码如下
java
public class CusLoadSpringFactories {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = CusLoadSpringFactories.class.getClassLoader();
Enumeration<URL> resources = classLoader.getResources("META-INF/spring.factories");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
System.out.println(url);
}
}
}
输出结果
javascript
file:/D:/code/test-web/target/classes/META-INF/spring.factories
jar:file:/E:/config/mavenrepository/org/springframework/boot/spring-boot/3.2.0/spring-boot-3.2.0.jar!/META-INF/spring.factories
jar:file:/E:/config/mavenrepository/org/springframework/boot/spring-boot-autoconfigure/3.2.0/spring-boot-autoconfigure-3.2.0.jar!/META-INF/spring.factories
jar:file:/E:/config/mavenrepository/org/springframework/spring-aop/6.1.1/spring-aop-6.1.1.jar!/META-INF/spring.factories
ClassLoader.getResources 分析
主要分析的就是上述测试代码中的以下三行代码
java
Enumeration<URL> resources = classLoader.getResources("META-INF/spring.factories");
resources.hasMoreElements();
URL url = resources.nextElement();
getResources() 方法
java
public abstract class ClassLoader {
public Enumeration<URL> getResources(String name) throws IOException {
Objects.requireNonNull(name);
@SuppressWarnings("unchecked")
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
if (parent != null) {
tmp[0] = parent.getResources(name);
} else {
tmp[0] = BootLoader.findResources(name);
}
// 代码1
tmp[1] = findResources(name);
return new CompoundEnumeration<>(tmp);
}
protected Enumeration<URL> findResources(String name) throws IOException {
return Collections.emptyEnumeration();
}
}
对于资源文件加载也是和类加载一样,遵循双亲委派模式,在创建的 CompoundEnumeration
中主要包含 Enumeration<URL>[] tmp
数组,重点关注的是 findResources
方法返回的 Enumeration
,因为目前使用的都是 应用程序类加载器加载的
findResources() 方法
从上面代码也能看出来,ClassLoader
是一个抽象类,findResources
在 ClassLoader
中默认实现为空,所以肯定是在子类中实现,看一下 AppClassLoader
的继承关系
findResources
实际是在 BuiltinClassLoader
类中实现的
java
public class BuiltinClassLoader extends SecureClassLoader {
@Override
public Enumeration<URL> findResources(String name) throws IOException {
List<URL> checked = new ArrayList<>(); // list of checked URLs
// 省略部分代码......
// 核心代码1
Enumeration<URL> e = findResourcesOnClassPath(name);
// concat the checked URLs and the (not checked) class path
// 核心代码2
return new Enumeration<>() {
final Iterator<URL> iterator = checked.iterator();
URL next;
private boolean hasNext() {
if (next != null) {
return true;
} else if (iterator.hasNext()) {
next = iterator.next();
return true;
} else {
// need to check each URL
while (e.hasMoreElements() && next == null) {
next = checkURL(e.nextElement());
}
return next != null;
}
}
@Override
public boolean hasMoreElements() {
return hasNext();
}
@Override
public URL nextElement() {
if (hasNext()) {
URL result = next;
next = null;
return result;
} else {
throw new NoSuchElementException();
}
}
};
}
}
findResources
方法中返回的是一个Enumeration
类型的匿名实现类,先看一下Enumeration
接口包含哪些方法
Enumeration 接口
java
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
有两个方法,一个是判断是否还有元素,一个是获取下一个元素。在 BuiltinClassLoader.findResources
方法中虽然返回来匿名实现类,但是真正的元素获取并不是这个类完成的,而是 Enumeration<URL> e = findResourcesOnClassPath(name);
方法中返回的 Enumeration
实现类完成的,如下图所示
findResourcesOnClassPath
代码如下
java
public class BuiltinClassLoader extends SecureClassLoader {
private @Stable URLClassPath ucp;
@SuppressWarnings("removal")
private Enumeration<URL> findResourcesOnClassPath(String name) {
if (hasClassPath()) {
// 默认是走的这里的逻辑
if (System.getSecurityManager() == null) {
return ucp.findResources(name, false);
} else {
PrivilegedAction<Enumeration<URL>> pa;
pa = () -> ucp.findResources(name, false);
return AccessController.doPrivileged(pa);
}
} else {
// no class path
return Collections.emptyEnumeration();
}
}
}
findResourcesOnClassPath
方法最终是调用 ucp.findResources(name, false)
, 这里 ucp
是 URLClassPath
类型,先看一下获取的 URLClassPath
中的包含的内容有哪些,该对象中的元素内容参考下图
既然最终获取资源的核心逻辑是 URLClassPath
类实现的,重点就看下这个类的方法
URLClassPath 类方法
java
public class URLClassPath {
public Enumeration<URL> findResources(final String name,
final boolean check) {
return new Enumeration<>() {
private int index = 0;
private URL url = null;
private boolean next() {
if (url != null) {
return true;
} else {
Loader loader;
// 这里的 Loader 有两个
while ((loader = getLoader(index++)) != null) {
url = loader.findResource(name, check);
if (url != null) {
return true;
}
}
return false;
}
}
public boolean hasMoreElements() {
return next();
}
public URL nextElement() {
if (!next()) {
throw new NoSuchElementException();
}
URL u = url;
url = null;
return u;
}
};
}
}
重点是上面的 Loader
, 主要有两类,一个是 FileLoader
, 一个是 JarLoader
-
FileLoader
就是加载当前项目非jar
包下的内容 -
JarLoader
是加载jar
下的文件
总结
Enumeration<URL> resources = classLoader.getResources("xxx")
方法返回的是Enumeration
类型,该类型是一个接口,主要有两个方法boolean hasMoreElements();
E nextElement();
Enumeration
是接口,最终调用的还是URLClassPath
类中public Enumeration<URL> findResources()
方法, 该方法会通过FileLoader
和JarLoader
去实际加载classpath
下的文件