<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆 - 4, 文章 - 0, 評論 - 2, 引用 - 0
    數據加載中……

    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: 

    Bootstrap ClassLoader
             ↑
    Extension ClassLoader
             ↑
    System ClassLoader
             ↑
    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
    5 }

    輸出如下: 

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

    我們從而得出Groovy的ClassLoader體系: 

                null                      // 即Bootstrap ClassLoader
                 ↑
    sun.misc.Launcher.ExtClassLoader      // 即Extension ClassLoader
                 ↑
    sun.misc.Launcher.AppClassLoader      // 即System ClassLoader
                 ↑
    org.codehaus.groovy.tools.RootLoader  // 以下為User Custom ClassLoader
                 ↑
    groovy.lang.GroovyClassLoader
                 ↑
    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);
    18 }

    這里的LoaderConfiguration是用來做什么的呢?它是用來解析$GROOVY_HOME/conf/groovy-starter.conf文件的,該文件內容如下(去掉了注釋部分): 

    load !{groovy.home}/lib/*.jar
    load !{user.home}/.groovy/lib/*.jar
    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     }
    13 }

    可以看到,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): 

    GroovyStarter
        ↓ (RootLoader)
    GroovyMain
        ↓
    GroovyShell
        ↓ (GroovyClassLoader)
    SomeScript

    RootLoader 

    RootLoader作為Groovy的根ClassLoader,負責加載Groovy及其依賴的第三方庫中的類。它管理了Groovy的classpath,我們可以通過$GROOVY_HOME/conf/groovy-starter.conf文件或groovy的命令行參數“-classpath”往其中添加路徑。注意,這有別于java的命令行參數“-classpath”定義的classpath,RootLoader中的classpath對Java原有的ClassLoader是不可見的。 

    我們先通過一個腳本來看一下RootLoader是如何體現為Groovy的classpath管理者的: 

     1 class C {}
     2 
     3 println this.class.classLoader
     4 println C.classLoader
     5 println()
     6 
     7 println groovy.ui.GroovyMain.classLoader
     8 println org.objectweb.asm.ClassVisitor.classLoader
     9 println()
    10 
    11 println String.classLoader
    12 println()
    13 
    14 println org.codehaus.groovy.tools.GroovyStarter.classLoader
    15 println ClassLoader.systemClassLoader.findLoadedClass('org.codehaus.groovy.tools.GroovyStarter')?.classLoader
    16 println()

    輸出如下: 

    groovy.lang.GroovyClassLoader$InnerLoader@1ba6076
    groovy.lang.GroovyClassLoader$InnerLoader@1ba6076

    org.codehaus.groovy.tools.RootLoader@a97b0b
    org.codehaus.groovy.tools.RootLoader@a97b0b

    null

    org.codehaus.groovy.tools.RootLoader@a97b0b
    sun.misc.Launcher$AppClassLoader@192d342

    • 腳本類和C類的ClassLoader是GroovyClassLoader.InnerLoader,這是我們預期內的。
    • GroovyMain的ClassLoader是RootLoader,是因為GroovyStarter就是用RootLoader來加載它的;而ClassVisitor是Groovy依賴的asm庫中的類,這個庫的jar文件路徑不在Java的classpath中,而是在Groovy的classpath中,所以很自然的,它的ClassLoader也是RootLoader。
    • String的ClassLoader是null,這是因為JDK中的基本類型都必須由Bootstrap ClassLoader加載(如果允許自定義的ClassLoader加載,那就天下大亂了)。
    • GroovyStarter的ClassLoader是RootLoader,這點讓我們很意外,GroovyStarter應該已經由System ClassLoader載入(systemClassLoader.findLoadedClass證實了這個想法),根據雙親委派模型,System ClassLoader的后代都不會嘗試去加載這個類,為什么RootLoader又去加載了一次GroovyStarter呢?

    答案很簡單,因為RootLoader沒有遵循雙親委派模型。我們來看一下RootLoader的loadClass方法(做了一些簡單的方法展開): 

     1 protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
     2     Class c = this.findLoadedClass(name);
     3     if (c != nullreturn c;
     4     c = (Class) customClasses.get(name); // customClasses定義了一些必須由Java原有ClassLoader載入的類
     5     if (c != nullreturn c;
     6 
     7     try {
     8         c = super.findClass(name); // 先嘗試加載這個類
     9     } catch (ClassNotFoundException cnfe) {
    10         // IGNORE
    11     }
    12     if (c == null) c = super.loadClass(name, resolve); // 加載不到則回到原有的雙親委派模型
    13 
    14     if (resolve) resolveClass(c);
    15 
    16     return c;
    17 }

    RootLoader先嘗試加載類,如果加載不到,再委派給parent加載,所以即使parent已經載入了GroovyStarter,RootLoader還會再加載一次。 

    為什么要這樣做的?道理很簡單。在前文中,我一再提醒大家,Java的classpath中只包含了Groovy的jar包,而不包含Groovy依賴的第三方jar包,而Groovy的classpath則包含了Groovy以及其依賴的所有第三方jar包。如果RootLoader使用雙親委派模型,那么Groovy的jar包中的類就會由System ClassLoader加載,當解析Groovy的類時,需要加載第三方的jar包,這時System ClassLoader并不知道從哪里加載,導致找不到類。因此RootLoader并沒有使用雙親委派模型。 

    可能你有疑問:為什么不把這些jar包都加入Java的classpath中?這樣不就不會有這個問題了嗎?確實如此,但是Groovy可以通過多種方式更靈活的往自己的classpath中添加路徑(你甚至可以通過代碼往RootLoader的classpath中添加路徑),而Java的classpath只能通過命令行添加,因此就有了RootLoader這樣的設計。 

    GroovyClassLoader 

    GroovyClassLoader主要負責在運行時編譯groovy源代碼為Class的工作,從而使Groovy實現了將groovy源代碼動態加載為Class的功能。 

    GroovyClassLoader編譯groovy代碼的工作重要集中到doParseClass方法中: 

     1 private Class doParseClass(GroovyCodeSource codeSource) {
     2     validate(codeSource); // 簡單校驗一些參數是否為null
     3     Class answer;  // Was neither already loaded nor compiling, so compile and add to cache.
     4     CompilationUnit unit = createCompilationUnit(config, codeSource.getCodeSource());
     5     SourceUnit su = null;
     6     if (codeSource.getFile() == null) {
     7         su = unit.addSource(codeSource.getName(), codeSource.getScriptText());
     8     } else {
     9         su = unit.addSource(codeSource.getFile());
    10     }
    11 
    12     ClassCollector collector = createCollector(unit, su); // 這里創建了InnerLoader
    13     unit.setClassgenCallback(collector);
    14     int goalPhase = Phases.CLASS_GENERATION;
    15     if (config != null && config.getTargetDirectory() != null) goalPhase = Phases.OUTPUT;
    16     unit.compile(goalPhase); // 編譯groovy源代碼
    17 
    18     // 查找源文件中的Main Class
    19     answer = collector.generatedClass;
    20     String mainClass = su.getAST().getMainClassName();
    21     for (Object o : collector.getLoadedClasses()) {
    22         Class clazz = (Class) o;
    23         String clazzName = clazz.getName();
    24         definePackage(clazzName);
    25         setClassCacheEntry(clazz);
    26         if (clazzName.equals(mainClass)) answer = clazz;
    27     }
    28     return answer;
    29 }

    如何編譯groovy源代碼已超出本文的范疇,因此不再介紹具體過程。 

    GroovyClassLoader.InnerLoader 

    我們繼續來看一下GroovyClassLoader的createCollector方法: 

     1 protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
     2     InnerLoader loader = AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
     3         public InnerLoader run() {
     4             return new InnerLoader(GroovyClassLoader.this);
     5         }
     6     });
     7     return new ClassCollector(loader, unit, su);
     8 }
     9 
    10 public static class ClassCollector extends CompilationUnit.ClassgenCallback {
    11     private final GroovyClassLoader cl;
    12     // 
    13     protected ClassCollector(InnerLoader cl, CompilationUnit unit, SourceUnit su) {
    14         this.cl = cl;
    15         // 
    16     }
    17     public GroovyClassLoader getDefiningClassLoader() {
    18         return cl;
    19     }
    20     protected Class createClass(byte[] code, ClassNode classNode) {
    21         GroovyClassLoader cl = getDefiningClassLoader();
    22         Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource()); // 通過InnerLoader加載該類
    23         this.loadedClasses.add(theClass);
    24         // 
    25         return theClass;
    26     }
    27     // 
    28 }

    我們可以看出,ClassCollector的作用,就是在編譯的過程中,將編譯出來的字節碼,通過InnerLoader進行加載。另外,每次編譯groovy源代碼的時候,都會新建一個InnerLoader的實例。 

    InnerLoader是如何加載這些類的呢?它將所有的加載工作又委派回給GroovyClassLoader。由于InnerLoader的代碼簡單,這里就不貼出來了。 

    那有了GroovyClassLoader,為什么還需要InnerLoader呢?主要有兩個原因: 

    • 由于一個ClassLoader對于同一個名字的類只能加載一次,如果都由GroovyClassLoader加載,那么當一個腳本里定義了C這個類之后,另外一個腳本再定義一個C類的話,GroovyClassLoader就無法加載了。
    • 由于當一個類的ClassLoader被GC之后,這個類才能被GC,如果由GroovyClassLoader加載所有的類,那么只有當GroovyClassLoader被GC了,所有這些類才能被GC,而如果用InnerLoader的話,由于編譯完源代碼之后,已經沒有對它的外部引用,除了它加載的類,所以只要它加載的類沒有被引用之后,它以及它加載的類就都可以被GC了。

    總結 

    本文介紹了Groovy中最主要的3個ClassLoader: 

    • RootLoader:管理了Groovy的classpath,負責加載Groovy及其依賴的第三方庫中的類,它不是使用雙親委派模型。
    • GroovyClassLoader:負責在運行時編譯groovy源代碼為Class的工作,從而使Groovy實現了將groovy源代碼動態加載為Class的功能。
    • GroovyClassLoader.InnerLoader:Groovy腳本類的直接ClassLoader,它將加載工作委派給GroovyClassLoader,它的存在是為了支持不同源碼里使用相同的類名,以及加載的類能順利被GC。

    以上分析有不當之處敬請指出,謝謝大家的閱讀。

    posted on 2013-04-14 22:54 Johnny Jian 閱讀(6796) 評論(1)  編輯  收藏 所屬分類: Groovy

    評論

    # re: Groovy深入探索——Groovy的ClassLoader體系  回復  更多評論   

    分析得真好!
    2014-04-07 23:24 | 劍痕
    主站蜘蛛池模板: 亚洲春色在线观看| 又粗又硬免费毛片| 久久精品国产亚洲AV果冻传媒| 美景之屋4在线未删减免费| 日韩a级毛片免费观看| 亚洲日韩精品无码专区| 午夜网站免费版在线观看| 亚洲中文字幕久久久一区| 成人黄动漫画免费网站视频 | 亚洲综合另类小说色区色噜噜| 国产成人亚洲综合a∨| 国产国产人免费人成免费视频| 美女扒开尿口给男人爽免费视频 | 国产亚洲精品仙踪林在线播放| 国产片免费在线观看| 曰批全过程免费视频免费看 | 中国一级特黄高清免费的大片中国一级黄色片 | 久久精品熟女亚洲av麻豆| 国产又黄又爽又猛的免费视频播放| 美女视频黄频a免费大全视频| 亚洲男人在线无码视频| 国产成人免费视频| 亚洲国产精品综合一区在线| 毛片免费全部免费观看| 粉色视频免费入口| 亚洲精品乱码久久久久久| 18pao国产成视频永久免费| 国产精品亚洲四区在线观看| 又大又硬又爽免费视频| 最近中文字幕免费大全| 亚洲精品美女久久久久| 国产精品冒白浆免费视频| 中国好声音第二季免费播放| 亚洲va精品中文字幕| 亚洲国产婷婷香蕉久久久久久| 久久久久国产精品免费网站| 亚洲另类自拍丝袜第五页| 亚洲日本va中文字幕久久| 国产在线a免费观看| 久香草视频在线观看免费| 亚洲自偷精品视频自拍|