隨筆 - 4, 文章 - 0, 評(píng)論 - 2, 引用 - 0
          數(shù)據(jù)加載中……

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

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

          注:以下分析的Groovy源代碼來自Groovy 2.1.3。 

          Java的ClassLoader 

          顧名思義,Java的ClassLoader就是類的裝載器,它使JVM可以動(dòng)態(tài)的載入Java類,JVM并不需要知道從什么地方(本地文件、網(wǎng)絡(luò)等)載入Java類,這些都由ClassLoader完成。 

          可以說,ClassLoader是Class的命名空間。同一個(gè)名字的類可以由多個(gè)ClassLoader載入,由不同ClassLoader載入的相同名字的類將被認(rèn)為是不同的類;而同一個(gè)ClassLoader對(duì)同一個(gè)名字的類只能載入一次。 

          Java的ClassLoader有一個(gè)著名的雙親委派模型(Parent Delegation Model):除了Bootstrap ClassLoader外,每個(gè)ClassLoader都有一個(gè)parent的ClassLoader,沿著parent最終會(huì)追索到Bootstrap ClassLoader;當(dāng)一個(gè)ClassLoader要載入一個(gè)類時(shí),會(huì)首先委派給parent,如果parent能載入這個(gè)類,則返回,否則這個(gè)ClassLoader才會(huì)嘗試去載入這個(gè)類。 

          Java的ClassLoader體系如下,其中箭頭指向的是該ClassLoader的parent: 

          Bootstrap ClassLoader
                   ↑
          Extension ClassLoader
                   ↑
          System ClassLoader
                   ↑
          User Custom ClassLoader  // 不一定有

          更多關(guān)于Java的ClassLoader的信息請(qǐng)參考以下資料: 


          Groovy的ClassLoader 

          我們首先通過一個(gè)腳本來看一下,一個(gè)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腳本啟動(dòng)過程 

          要介紹RootLoader前,我們需要介紹一下Groovy腳本的啟動(dòng)過程。 

          當(dāng)我們?cè)诿钚休斎?#8220;groovy SomeScript”來運(yùn)行腳本時(shí),調(diào)用的是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

          我們可以發(fā)現(xiàn),這里其實(shí)是通過java啟動(dòng)了org.codehaus.groovy.tools.GroovyStarter,然后把“--main groovy.ui.GroovyMain”作為參數(shù)傳給GroovyStarter,最后又把SomeScript作為參數(shù)傳給GroovyMain。注意,這里只把$GROOVY_HOME/lib/groovy-2.1.3.jar作為classpath參數(shù)傳給了JVM,而不包含Groovy依賴的第三方j(luò)ar包。 

          我們來看一下GroovyStarter的源代碼(其中省略了異常處理的代碼): 

           1 public static void rootLoader(String args[]) {
           2     String conf = System.getProperty("groovy.starter.conf",null);
           3     LoaderConfiguration lc = new LoaderConfiguration();
           4     // 這里省略了解析命令行參數(shù)的代碼
           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}); // 調(diào)用GroovyMain的main方法
          14 }
          15 // 
          16 public static void main(String args[]) {
          17     rootLoader(args);
          18 }

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

          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依賴的第三方j(luò)ar包。 

          接下來,我們來看一下GroovyMain的源代碼。GroovyMain的main函數(shù)進(jìn)去之后,最終會(huì)到達(dá)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); // 本地腳本文件執(zhí)行這行
           9         }
          10     } else {
          11         groovy.run(script, "script_from_command_line", args);
          12     }
          13 }

          可以看到,GroovyMain是通過GroovyShell來執(zhí)行腳本文件的,GroovyShell的具體執(zhí)行腳本的代碼我們不再分析,我們只看GroovyShell的構(gòu)造函數(shù)中初始化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。 

          最后來總結(jié)一下Groovy腳本的啟動(dòng)流程(括號(hào)中表示使用的ClassLoader): 

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

          RootLoader 

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

          我們先通過一個(gè)腳本來看一下RootLoader是如何體現(xiàn)為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,這是我們預(yù)期內(nèi)的。
          • GroovyMain的ClassLoader是RootLoader,是因?yàn)镚roovyStarter就是用RootLoader來加載它的;而ClassVisitor是Groovy依賴的asm庫(kù)中的類,這個(gè)庫(kù)的jar文件路徑不在Java的classpath中,而是在Groovy的classpath中,所以很自然的,它的ClassLoader也是RootLoader。
          • String的ClassLoader是null,這是因?yàn)镴DK中的基本類型都必須由Bootstrap ClassLoader加載(如果允許自定義的ClassLoader加載,那就天下大亂了)。
          • GroovyStarter的ClassLoader是RootLoader,這點(diǎn)讓我們很意外,GroovyStarter應(yīng)該已經(jīng)由System ClassLoader載入(systemClassLoader.findLoadedClass證實(shí)了這個(gè)想法),根據(jù)雙親委派模型,System ClassLoader的后代都不會(huì)嘗試去加載這個(gè)類,為什么RootLoader又去加載了一次GroovyStarter呢?

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

           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); // 先嘗試加載這個(gè)類
           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已經(jīng)載入了GroovyStarter,RootLoader還會(huì)再加載一次。 

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

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

          GroovyClassLoader 

          GroovyClassLoader主要負(fù)責(zé)在運(yùn)行時(shí)編譯groovy源代碼為Class的工作,從而使Groovy實(shí)現(xiàn)了將groovy源代碼動(dòng)態(tài)加載為Class的功能。 

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

           1 private Class doParseClass(GroovyCodeSource codeSource) {
           2     validate(codeSource); // 簡(jiǎn)單校驗(yàn)一些參數(shù)是否為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); // 這里創(chuàng)建了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 

          我們繼續(xù)來看一下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的作用,就是在編譯的過程中,將編譯出來的字節(jié)碼,通過InnerLoader進(jìn)行加載。另外,每次編譯groovy源代碼的時(shí)候,都會(huì)新建一個(gè)InnerLoader的實(shí)例。 

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

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

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

          總結(jié) 

          本文介紹了Groovy中最主要的3個(gè)ClassLoader: 

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

          以上分析有不當(dāng)之處敬請(qǐng)指出,謝謝大家的閱讀。

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

          評(píng)論

          # re: Groovy深入探索——Groovy的ClassLoader體系  回復(fù)  更多評(píng)論   

          分析得真好!
          2014-04-07 23:24 | 劍痕
          主站蜘蛛池模板: 桐城市| 石嘴山市| 成安县| 喀喇沁旗| 同江市| 峨眉山市| 伽师县| 惠来县| 句容市| 沁水县| 尉氏县| 昭通市| 澄城县| 龙川县| 德阳市| 蕲春县| 都江堰市| 苍梧县| 东莞市| 什邡市| 巢湖市| 锦州市| 顺义区| 平陆县| 三原县| 昌邑市| 榆社县| 陆良县| 青州市| 西盟| 嘉鱼县| 乌审旗| 汶上县| 子洲县| 栾城县| 大田县| 龙南县| 康定县| 博湖县| 通辽市| 鄂温|