中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何深入探索Groovy的ClassLoader體系

發布時間:2021-10-29 16:54:37 來源:億速云 閱讀:143 作者:柒染 欄目:編程語言

如何深入探索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  // 不一定有

Groovy的ClassLoader

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

def cl = this.class.classLoader  while (cl) {      println cl      cl = cl.parent  }

輸出如下: 

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:

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

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

#…  STARTER_CLASSPATH="$GROOVY_HOME/lib/groovy-2.1.3.jar" #…  startGroovy ( ) {      CLASS=$1     shift      # Start the Profiler or the JVM      if $useprofiler ; then          runProfiler      else         exec "$JAVACMD" $JAVA_OPTS \              -classpath "$STARTER_CLASSPATH" \              -Dscript.name="$SCRIPT_PATH" \              -Dprogram.name="$PROGNAME" \              -Dgroovy.starter.conf="$GROOVY_CONF" \              -Dgroovy.home="$GROOVY_HOME" \              -Dtools.jar="$TOOLS_JAR" \              $STARTER_MAIN_CLASS \              --main $CLASS \              --conf "$GROOVY_CONF" \              --classpath "$CP" \              "$@"     fi  }   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的源代碼(其中省略了異常處理的代碼):

public static void rootLoader(String args[]) {      String conf = System.getProperty("groovy.starter.conf",null);      LoaderConfiguration lc = new LoaderConfiguration();      // 這里省略了解析命令行參數的代碼      // load configuration file      if (conf!=null) {          lc.configure(new FileInputStream(conf));      }      // create loader and execute main class      ClassLoader loader = new RootLoader(lc);      Class c = loader.loadClass(lc.getMainClass()); // 使用RootLoader載入GroovyMain      Method m = c.getMethod("main", new Class[]{String[].class});      m.invoke(null, new Object[]{newArgs}); // 調用GroovyMain的main方法  }  //   public static void main(String args[]) {      rootLoader(args);  }

這里的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方法:

private void processOnce() throws CompilationFailedException, IOException {      GroovyShell groovy = new GroovyShell(conf);       if (isScriptFile) {          if (isScriptUrl(script)) {              groovy.run(getText(script), script.substring(script.lastIndexOf("/") + 1), args);          } else {              groovy.run(huntForTheScriptFile(script), args); // 本地腳本文件執行這行          }      } else {          groovy.run(script, "script_from_command_line", args);      }  }

可以看到,GroovyMain是通過GroovyShell來執行腳本文件的,GroovyShell的具體執行腳本的代碼我們不再分析,我們只看GroovyShell的構造函數中初始化ClassLoader的代碼:

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

由此可見,GroovyShell使用了GroovyClassLoader來加載類,而該GroovyClassLoader的parent即為GroovyShell的ClassLoader,也就是GroovyMain的ClassLoader,也就是RootLoader。

***來總結一下Groovy腳本的啟動流程(括號中表示使用的ClassLoader):

GroovyStarter      &darr; (RootLoader)  GroovyMain      &darr;  GroovyShell      &darr; (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管理者的:

class C {}   println this.class.classLoader  println C.classLoader  println()   println groovy.ui.GroovyMain.classLoader  println org.objectweb.asm.ClassVisitor.classLoader  println()   println String.classLoader  println()   println org.codehaus.groovy.tools.GroovyStarter.classLoader  println ClassLoader.systemClassLoader.findLoadedClass('org.codehaus.groovy.tools.GroovyStarter')?.classLoader  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方法(做了一些簡單的方法展開): 

protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {      Class c = this.findLoadedClass(name);      if (c != null) return c;      c = (Class) customClasses.get(name); // customClasses定義了一些必須由Java原有ClassLoader載入的類      if (c != null) return c;      try {          c = super.findClass(name); // 先嘗試加載這個類      } catch (ClassNotFoundException cnfe) {          // IGNORE      }      if (c == null) c = super.loadClass(name, resolve); // 加載不到則回到原有的雙親委派模型       if (resolve) resolveClass(c);       return c;  }

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方法中:

private Class doParseClass(GroovyCodeSource codeSource) {      validate(codeSource); // 簡單校驗一些參數是否為null      Class answer;  // Was neither already loaded nor compiling, so compile and add to cache.      CompilationUnit unit = createCompilationUnit(config, codeSource.getCodeSource());      SourceUnit su = null;      if (codeSource.getFile() == null) {          su = unit.addSource(codeSource.getName(), codeSource.getScriptText());      } else {          su = unit.addSource(codeSource.getFile());      }       ClassCollector collector = createCollector(unit, su); // 這里創建了InnerLoader      unit.setClassgenCallback(collector);      int goalPhase = Phases.CLASS_GENERATION;      if (config != null && config.getTargetDirectory() != null) goalPhase = Phases.OUTPUT;      unit.compile(goalPhase); // 編譯groovy源代碼       // 查找源文件中的Main Class      answer = collector.generatedClass;      String mainClass = su.getAST().getMainClassName();      for (Object o : collector.getLoadedClasses()) {          Class clazz = (Class) o;          String clazzName = clazz.getName();          definePackage(clazzName);          setClassCacheEntry(clazz);          if (clazzName.equals(mainClass)) answer = clazz;      }      return answer;  }

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

GroovyClassLoader.InnerLoader

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

protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {      InnerLoader loader = AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {          public InnerLoader run() {              return new InnerLoader(GroovyClassLoader.this);          }      });      return new ClassCollector(loader, unit, su);  }   public static class ClassCollector extends CompilationUnit.ClassgenCallback {      private final GroovyClassLoader cl;      // &hellip;&hellip;      protected ClassCollector(InnerLoader cl, CompilationUnit unit, SourceUnit su) {          this.cl = cl;          // &hellip;&hellip;      }      public GroovyClassLoader getDefiningClassLoader() {          return cl;      }      protected Class createClass(byte[] code, ClassNode classNode) {          GroovyClassLoader cl = getDefiningClassLoader();          Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource()); // 通過InnerLoader加載該類          this.loadedClasses.add(theClass);          // &hellip;&hellip;          return theClass;      }      // &hellip;&hellip;  }

我們可以看出,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。 

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

平湖市| 咸宁市| 温宿县| 奉化市| 河曲县| 华池县| 祁阳县| 阳原县| 沙田区| 绥中县| 道孚县| 徐水县| 百色市| 巴楚县| 环江| 庐江县| 两当县| 曲松县| 古浪县| 陈巴尔虎旗| 缙云县| 桓台县| 衡南县| 齐河县| 克什克腾旗| 沾化县| 闸北区| 迭部县| 凤凰县| 富裕县| 景泰县| 华阴市| 武汉市| 太原市| 巫山县| 云和县| 边坝县| 航空| 乐陵市| 长武县| 固安县|