ClassLoader【0】 前言

181 阅读3分钟

这是我参与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装载的,而是在初始化生成的时候,就导入的。