ClassLoader加载Resource的源码分析

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 是一个抽象类,findResourcesClassLoader 中默认实现为空,所以肯定是在子类中实现,看一下 AppClassLoader 的继承关系

classDiagram class A["ClassLoader"] class B["BuiltinClassLoader"] class C["AppClassLoader"] A <|-- B B <|-- C

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), 这里 ucpURLClassPath 类型,先看一下获取的 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() 方法, 该方法会通过 FileLoaderJarLoader 去实际加载 classpath 下的文件
相关推荐
monkey_meng18 分钟前
【Rust类型驱动开发 Type Driven Development】
开发语言·后端·rust
落落落sss26 分钟前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby
我救我自己26 分钟前
UE5运行时创建slate窗口
java·服务器·ue5
2401_853275731 小时前
ArrayList 源码分析
java·开发语言
爪哇学长1 小时前
SQL 注入详解:原理、危害与防范措施
xml·java·数据库·sql·oracle
大鲤余1 小时前
Rust,删除cargo安装的可执行文件
开发语言·后端·rust
她说彩礼65万1 小时前
Asp.NET Core Mvc中一个视图怎么设置多个强数据类型
后端·asp.net·mvc
MoFe11 小时前
【.net core】【sqlsugar】字符串拼接+内容去重
java·开发语言·.netcore
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
_江南一点雨1 小时前
SpringBoot 3.3.5 试用CRaC,启动速度提升3到10倍
java·spring boot·后端