这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」
参考:
前言
在Spring的自动装配章节中,我们可以知道:
- 各个中间件的AutoConfiguration,都是从
classLoader.getResources("META-INF/spring.factories")
这一行代码中解析、缓存的。
根据这个信息以及相关的这些中间件中声明AutoConfiguration的地方是在对应中间件的包下的META-INF/spring.factories,猜测这个方法应该是从配置的文件中读出来的,但是:
- 我们是从SpringFactoriesLoader中调用的这个方法,而这些classLoader只有一个,并且没有指定包的绝对路径,那么这里是怎么根据一个相对路径,来获取到这么多包下配置文件的绝对路径并提取出来的?
这就是我们探究ClassLoader的原因。
调用
在自动装配的ImportAutoConfigurationImportSelector中,classLoader是这么传入的:
SpringFactoriesLoader.loadFactoryNames(source, getClass().getClassLoader());
//下面省略调用的具体的,这个参数就没变过
//这里的getClass,得到的参数是ImportAutoConfigurationImportSelector
通过debug,可以看到:这里的classLoader对应的是AppClassLoader。
然后我们载入资源的方式是:classLoader.getResources("META-INF/spring.factories")
而这里的getResources,对应源码如下:
public Enumeration<URL> getResources(String name) throws IOException {
@SuppressWarnings("unchecked")
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
if (parent != null) {
tmp[0] = parent.getResources(name);
} else {
tmp[0] = getBootstrapResources(name);
}
tmp[1] = findResources(name);
return new CompoundEnumeration<>(tmp);
}
这里其实也能看到ClassLoader中双亲的概念,看到这个parent了没有?然后往上大家的祖宗都是这个getBootstrapResources
通过debug可以看到这里拿到的CompoundEnumeration<>(tmp) 中包含了所有类的url。
通过参考中的文章,以及这里我们指定的classLoader是appClassLoader,可以知道:
应用程序加载器(Application Classloader)也叫系统类加载器,它负责加载用户路径(ClassPath)上所指定的类库。我们自己编写的代码以及使用的第三方的jar包都是由它来加载的。
这里用户路径,指的就是服务的应用。
这里参照getBootstrapResources:
private static Enumeration<URL> getBootstrapResources(String name) throws IOException { final Enumeration<Resource> e = getBootstrapClassPath().getResources(name); return new Enumeration<URL> () { public URL nextElement() { return e.nextElement().getURL(); } public boolean hasMoreElements() { return e.hasMoreElements(); } }; } //这里的getBootstrapClassPath: static URLClassPath getBootstrapClassPath() { return sun.misc.Launcher.getBootstrapClassPath(); } //再往下的代码就不贴了,就是会将缓存中的数据返回,这里涉及到VM级别的运行,就不往下看了
结合入参,此时SpringFactoriesLoader.loadSpringFactories中的中的大致流程我们就能整个明白了:
- 这里会从ClassLoader中,获取到当前项目中(包括引入依赖的那些jar包),获取到所有相对路径是META-INF/spring.factories的文件中的信息,返回并保存。
这个过程是在java虚拟机层面的,并不是在Spring中执行的。
来都来了
参考文章追溯源码,现在清楚了:
-
Spring中,那些记录在 META-INF/spring.factories中的对应需要autoConfiguration的,是从启动路径
启动路径的获取:ClassLoader中的getBootstrapClassPath()
获取的。
问题解决了,也来看看ClassLoader的具体功能吧,毕竟来都来了。
在Spring中的调用我们清楚了:ClassLoader可以获取路径下对应名称的文件的URL,有了这个URL我们就可以去做下一步操作了,可以是解析,也可以是其他的操作。
这个classLoader很有意思,因为java中类都继承了Object,那么我们可以这么做:
//main就是一个简单类
main.class.getClassLoader().getClass().getClassLoader().getClass().getClassLoader()...
但这么做会报错,我们来逐层看一下。
main.getClassloader().getClass(),获取的class是sun.misc.Launcher$AppClassLoader
而对sun.misc.Launcher$AppClassLoader执行getClass().getClassLoader() 的操作,得出来的结果就是null。
这里就能看出来:
- 上面参考文章中的:三个在虚拟机中,会导入的classLoader,其中就包含了这个AppClassLoader,因此这个ClassLoader,并不是被某个在JDK环境中可以溯源的ClassLoader装载的,而是在初始化生成的时候,就导入的。