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的信息请参考以下资料:

  • 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.              ↑
  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. 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() {
  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

#p#

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. println this.class.classLoader
  3. println C.classLoader
  4. println()
  5. println groovy.ui.GroovyMain.classLoader
  6. println org.objectweb.asm.ClassVisitor.classLoader
  7. println()
  8. println String.classLoader
  9. println()
  10. println org.codehaus.groovy.tools.GroovyStarter.classLoader
  11. println ClassLoader.systemClassLoader.findLoadedClass('org.codehaus.groovy.tools.GroovyStarter')?.classLoader
  12. println()

输出如下:

 
 
 
  1. groovy.lang.GroovyClassLoader$InnerLoader@1ba6076
  2. groovy.lang.GroovyClassLoader$InnerLoader@1ba6076
  3. org.codehaus.groovy.tools.RootLoader@a97b0b
  4. org.codehaus.groovy.tools.RootLoader@a97b0b
  5. null
  6. org.codehaus.groovy.tools.RootLoader@a97b0b
  7. 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 != null) return c;
  4.     c = (Class) customClasses.get(name); // customClasses定义了一些必须由Java原有ClassLoader载入的类
  5.     if (c != null) return c;
  6.    try {
  7.         c = super.findClass(name); // 先尝试加载这个类
  8.     } catch (ClassNotFoundException cnfe) {
  9.         // IGNORE
  10.     }
  11.     if (c == null) c = super.loadClass(name, resolve); // 加载不到则回到原有的双亲委派模型
  12.     if (resolve) resolveClass(c);
  13.     return c;
  14. }

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.     ClassCollector collector = createCollector(unit, su); // 这里创建了InnerLoader
  12.     unit.setClassgenCallback(collector);
  13.     int goalPhase = Phases.CLASS_GENERATION;
  14.     if (config != null && config.getTargetDirectory() != null) goalPhase = Phases.OUTPUT;
  15.     unit.compile(goalPhase); // 编译groovy源代码
  16.     // 查找源文件中的Main Class
  17.     answer = collector.generatedClass;
  18.     String mainClass = su.getAST().getMainClassName();
  19.     for (Object o : collector.getLoadedClasses()) {
  20.         Class clazz = (Class) o;
  21.         String clazzName = clazz.getName();
  22.         definePackage(clazzName);
  23.         setClassCacheEntry(clazz);
  24.         if (clazzName.equals(mainClass)) answer = clazz;
  25.     }
  26.     return answer;
  27. }

如何编译groovy源代码已超出本文的范畴,因此不再介绍具体过程。

GroovyClassLoader.InnerLoader

我们继续来看一下GroovyClassLoader的createCollector方法:

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

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

新闻名称:Groovy深入探索:Groovy的ClassLoader体系
新闻来源:http://www.csdahua.cn/qtweb/news2/230752.html

网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网