Groovy深入探索——Groovy的ClassLoader体系

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:

  1. Bootstrap ClassLoader  
  2.          ↑  
  3. Extension ClassLoader  
  4.          ↑  
  5. System ClassLoader  
  6.          ↑  
  7. User Custom ClassLoader  // 不一定有 

更多关于Java的ClassLoader的信息请参考以下资料:  

Groovy的ClassLoader

我们首先通过一个脚本来看一下,一个Groovy脚本的ClassLoader以及它的祖先们分别是什么:

  1. def cl = this.class.classLoader  
  2. while (cl) {  
  3.     println cl  
  4.     cl = cl.parent  

输出如下: 

  1. groovy.lang.GroovyClassLoader$InnerLoader@18622f3  
  2. groovy.lang.GroovyClassLoader@147c1db  
  3. org.codehaus.groovy.tools.RootLoader@186db54  
  4. sun.misc.Launcher$AppClassLoader@192d342  
  5. sun.misc.Launcher$ExtClassLoader@6b97fd 

我们从而得出Groovy的ClassLoader体系:

  1.             null                      // 即Bootstrap ClassLoader  
  2.              ↑  
  3. sun.misc.Launcher.ExtClassLoader      // 即Extension ClassLoader  
  4.              ↑  
  5. sun.misc.Launcher.AppClassLoader      // 即System ClassLoader  
  6.              ↑  
  7. org.codehaus.groovy.tools.RootLoader  // 以下为User Custom ClassLoader  
  8.              ↑  
  9. groovy.lang.GroovyClassLoader  
  10.              ↑  
  11. groovy.lang.GroovyClassLoader.InnerLoader 

下面我们分别介绍一下RootLoader、GroovyClassLoader和GroovyClassLoader.InnerLoader。

Groovy脚本启动过程

要介绍RootLoader前,我们需要介绍一下Groovy脚本的启动过程。

当我们在命令行输入“groovy SomeScript”来运行脚本时,调用的是shell脚本$GROOVY_HOME/bin/groovy:

  1. #…   
  2. startGroovy groovy.ui.GroovyMain "$@" 

其中startGroovy定义在$GROOVY_HOME/bin/startGroovy中: 

  1. #…  
  2. STARTER_CLASSPATH="$GROOVY_HOME/lib/groovy-2.1.3.jar" 
  3. #…  
  4. startGroovy ( ) {  
  5.     CLASS=$1 
  6.     shift  
  7.     # Start the Profiler or the JVM  
  8.     if $useprofiler ; then  
  9.         runProfiler  
  10.     else 
  11.         exec "$JAVACMD" $JAVA_OPTS \  
  12.             -classpath "$STARTER_CLASSPATH" \  
  13.             -Dscript.name="$SCRIPT_PATH" \  
  14.             -Dprogram.name="$PROGNAME" \  
  15.             -Dgroovy.starter.conf="$GROOVY_CONF" \  
  16.             -Dgroovy.home="$GROOVY_HOME" \  
  17.             -Dtools.jar="$TOOLS_JAR" \  
  18.             $STARTER_MAIN_CLASS \  
  19.             --main $CLASS \  
  20.             --conf "$GROOVY_CONF" \  
  21.             --classpath "$CP" \  
  22.             "$@" 
  23.     fi  
  24. }  
  25.  
  26. STARTER_MAIN_CLASS=org.codehaus.groovy.tools.GroovyStarter 

我们可以发现,这里其实是通过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的源代码(其中省略了异常处理的代码):

  1. public static void rootLoader(String args[]) {  
  2.     String conf = System.getProperty("groovy.starter.conf",null);  
  3.     LoaderConfiguration lc = new LoaderConfiguration();  
  4.     // 这里省略了解析命令行参数的代码  
  5.     // load configuration file  
  6.     if (conf!=null) {  
  7.         lc.configure(new FileInputStream(conf));  
  8.     }  
  9.     // create loader and execute main class  
  10.     ClassLoader loader = new RootLoader(lc);  
  11.     Class c = loader.loadClass(lc.getMainClass()); // 使用RootLoader载入GroovyMain  
  12.     Method m = c.getMethod("main"new Class[]{String[].class});  
  13.     m.invoke(nullnew Object[]{newArgs}); // 调用GroovyMain的main方法  
  14. }  
  15. //   
  16. public static void main(String args[]) {  
  17.     rootLoader(args);  

这里的LoaderConfiguration是用来做什么的呢?它是用来解析$GROOVY_HOME/conf/groovy-starter.conf文件的,该文件内容如下(去掉了注释部分):

  1. load !{groovy.home}/lib/*.jar  
  2. load !{user.home}/.groovy/lib/*.jar  
  3. load ${tools.jar} 

这表示,将$GROOVY_HOME/lib/*.jar、$HOME/.groovy/lib/*.jar以及tools.jar加入到RootLoader的classpath中,可以看出,这里包含了Groovy依赖的第三方jar包。

接下来,我们来看一下GroovyMain的源代码。GroovyMain的main函数进去之后,最终会到达processOnce方法:

  1. private void processOnce() throws CompilationFailedException, IOException {  
  2.     GroovyShell groovy = new GroovyShell(conf);  
  3.  
  4.     if (isScriptFile) {  
  5.         if (isScriptUrl(script)) {  
  6.             groovy.run(getText(script), script.substring(script.lastIndexOf("/") + 1), args);  
  7.         } else {  
  8.             groovy.run(huntForTheScriptFile(script), args); // 本地脚本文件执行这行  
  9.         }  
  10.     } else {  
  11.         groovy.run(script, "script_from_command_line", args);  
  12.     }  

可以看到,GroovyMain是通过GroovyShell来执行脚本文件的,GroovyShell的具体执行脚本的代码我们不再分析,我们只看GroovyShell的构造函数中初始化ClassLoader的代码:

  1. final ClassLoader parentLoader = (parent!=null)?parent:GroovyShell.class.getClassLoader();  
  2. this.loader = AccessController.doPrivileged(new PrivilegedAction<GroovyClassLoader>() {  
  3.     public GroovyClassLoader run() {  
  4.         return new GroovyClassLoader(parentLoader,config);  
  5.     }  
  6. }); 

由此可见,GroovyShell使用了GroovyClassLoader来加载类,而该GroovyClassLoader的parent即为GroovyShell的ClassLoader,也就是GroovyMain的ClassLoader,也就是RootLoader。

最后来总结一下Groovy脚本的启动流程(括号中表示使用的ClassLoader):

  1. GroovyStarter  
  2.     ↓ (RootLoader)  
  3. GroovyMain  
  4.     ↓  
  5. GroovyShell  
  6.     ↓ (GroovyClassLoader)  
  7. SomeScript 

相关推荐