歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Groovy深入探索——Groovy的ClassLoader體系

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

日期:2017/3/1 9:38:40   编辑:Linux編程

Groovy中定義了不少ClassLoader,本文將介紹其中絕大多數Groovy腳本都會涉及到的,也是最主要的3個ClassLoader:RootLoader、GroovyClassLoader和GroovyClassLoader.InnerLoader。

Groovy入門教程 http://www.linuxidc.com/Linux/2013-09/89776.htm

注:以下分析的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. Extension ClassLoader
  3. System ClassLoader
  4. User Custom ClassLoader // 不一定有

更多關於Java的ClassLoader的信息請參考以下資料:

  • Java Classloader
  • Understanding Extension Class Loading
  • ClassLoader與JVM

Groovy的ClassLoader

我們首先通過一個腳本來看一下,一個Groovy腳本的ClassLoader以及它的祖先們分別是什麼:

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

輸出如下:

  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. sun.misc.Launcher.ExtClassLoader // 即Extension ClassLoader
  3. sun.misc.Launcher.AppClassLoader // 即System ClassLoader
  4. org.codehaus.groovy.tools.RootLoader // 以下為User Custom ClassLoader
  5. groovy.lang.GroovyClassLoader
  6. 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. 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(null, new Object[]{newArgs}); // 調用GroovyMain的main方法
  14. }
  15. //
  16. public static void main(String args[]) {
  17. rootLoader(args);
  18. }

這裡的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. if (isScriptFile) {
  4. if (isScriptUrl(script)) {
  5. groovy.run(getText(script), script.substring(script.lastIndexOf("/") + 1), args);
  6. } else {
  7. groovy.run(huntForTheScriptFile(script), args); // 本地腳本文件執行這行
  8. }
  9. } else {
  10. groovy.run(script, "script_from_command_line", args);
  11. }
  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. GroovyShell
  5. ↓ (GroovyClassLoader)
  6. SomeScript

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-10/108030p2.htm

Copyright © Linux教程網 All Rights Reserved