Groovy中定义了不少ClassLoader,本文将介绍其中绝大多数Groovy脚本都会涉及到的,也是最主要的3个ClassLoader:RootLoader、GroovyClassLoader和GroovyClassLoader.InnerLoader。
注:以下分析的Groovy源代码来自Groovy 2.1.3。
Java的ClassLoader
顾名思义,Java的ClassLoader就是类的装载器,它使JVM可以动态的载入Java类,JVM并不需要知道从什么地方(本地文件、网络等)载入Java类,这些都由ClassLoader完成。
可以说,ClassLoader是Class的命名空间。同一个名字的类可以由多个ClassLoader载入,由不同ClassLoader载入的相同名字的类将被认为是不同的类;而同一个ClassLoader对同一个名字的类只能载入一次。
Java的ClassLoader有一个著名的双亲委派模型(Parent Delegation Model):除了Bootstrap ClassLoader外,每个ClassLoader都有一个parent的ClassLoader,沿着parent最终会追索到Bootstrap ClassLoader;当一个ClassLoader要载入一个类时,会首先委派给parent,如果parent能载入这个类,则返回,否则这个ClassLoader才会尝试去载入这个类。
Java的ClassLoader体系如下,其中箭头指向的是该ClassLoader的parent:
更多关于Java的ClassLoader的信息请参考以下资料:
Groovy的ClassLoader
我们首先通过一个脚本来看一下,一个Groovy脚本的ClassLoader以及它的祖先们分别是什么:
输出如下:
我们从而得出Groovy的ClassLoader体系:
下面我们分别介绍一下RootLoader、GroovyClassLoader和GroovyClassLoader.InnerLoader。
Groovy脚本启动过程
要介绍RootLoader前,我们需要介绍一下Groovy脚本的启动过程。
当我们在命令行输入“groovy SomeScript”来运行脚本时,调用的是shell脚本$GROOVY_HOME/bin/groovy:
其中startGroovy定义在$GROOVY_HOME/bin/startGroovy中:
我们可以发现,这里其实是通过java启动了org.codehaus.groovy.tools.GroovyStarter,然后把“--main groovy.ui.GroovyMain”作为参数传给GroovyStarter,最后又把SomeScript作为参数传给GroovyMain。注意,这里只把$GROOVY_HOME/lib/groovy-2.1.3.jar作为classpath参数传给了JVM,而不包含Groovy依赖的第三方jar包。
我们来看一下GroovyStarter的源代码(其中省略了异常处理的代码):
这里的LoaderConfiguration是用来做什么的呢?它是用来解析$GROOVY_HOME/conf/groovy-starter.conf文件的,该文件内容如下(去掉了注释部分):
这表示,将$GROOVY_HOME/lib/*.jar、$HOME/.groovy/lib/*.jar以及tools.jar加入到RootLoader的classpath中,可以看出,这里包含了Groovy依赖的第三方jar包。
接下来,我们来看一下GroovyMain的源代码。GroovyMain的main函数进去之后,最终会到达processOnce方法:
可以看到,GroovyMain是通过GroovyShell来执行脚本文件的,GroovyShell的具体执行脚本的代码我们不再分析,我们只看GroovyShell的构造函数中初始化ClassLoader的代码:
由此可见,GroovyShell使用了GroovyClassLoader来加载类,而该GroovyClassLoader的parent即为GroovyShell的ClassLoader,也就是GroovyMain的ClassLoader,也就是RootLoader。
最后来总结一下Groovy脚本的启动流程(括号中表示使用的ClassLoader):