`
JohnnyJian
  • 浏览: 104244 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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以及它的祖先们分别是什么:
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
    ↓ (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管理者的:
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;
    // ...
    protected ClassCollector(InnerLoader cl, CompilationUnit unit, SourceUnit su) {
        this.cl = cl;
        // ...
    }
    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);
        // ...
        return theClass;
    }
    // ...
}

我们可以看出,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轻松入门——Grails实战基础篇

     0,下载Grails( http://dist.codehaus.org/grails/grails-bin-0.4.2.zip ,请留意朝花夕拾——Groovy & Grails中的“最新版本”提示)并解压到自己指定位置(我的位置是D:\D\MY_DEV\grails)  1,设置环境变量GRAILS...

    Groovy need not rails——介绍自己写的一个基于groovy的框架,Webx

    NULL 博文链接:https://key232323.iteye.com/blog/1336400

    groovy-3.0.9-API文档-中文版.zip

    赠送jar包:groovy-3.0.9.jar; 赠送原API文档:groovy-3.0.9-javadoc.jar; 赠送源代码:groovy-3.0.9-sources.jar; 赠送Maven依赖信息文件:groovy-3.0.9.pom; 包含翻译后的API文档:groovy-3.0.9-javadoc-API...

    apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本

    apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望...

    [Groovy] Making Java Groovy 英文版

    Making Java Groovy is a practical handbook for developers who want to blend Groovy into their day to day work with Java It starts by introducing the key differences between Java and Groovy and how you...

    Java调用Groovy,实时动态加载数据库groovy脚本

    Java调用Groovy,实时动态加载数据库groovy脚本,java读取mongoDB的groovy脚本,加载实时运行,热部署

    groovy入门经典,groovyeclipse 插件

    groovy入门经典,groovyeclipse 插件

    Groovy轻松入门—搭建Groovy开发环境

    Groovy轻松入门—搭建Groovy开发环境 Groovy轻松入门—搭建Groovy开发环境

    Groovy.in.Action.2nd.Edition.1935182

    Groovy in Action, Second Edition is the undisputed definitive reference on the Groovy language. Written by core members of the Groovy language team, this book presents Groovy like no other can—from ...

    groovy-3.0.9-API文档-中英对照版.zip

    赠送jar包:groovy-3.0.9.jar; 赠送原API文档:groovy-3.0.9-javadoc.jar; 赠送源代码:groovy-3.0.9-sources.jar; 赠送Maven依赖信息文件:groovy-3.0.9.pom; 包含翻译后的API文档:groovy-3.0.9-javadoc-API...

    groovy和Java相互调用1

    Groovy 调用 Java 类groovy 调用 Java class 十分方便,只需要在类前导入该 Java 类,在 Groovy 代码中就可以无缝使用该

    精通 Groovy--下一代开发语言

    什么是 Groovy? Groovy 是 JVM 的一个替代语言 — 替代 是指可以用 Groovy 在 Java 平台上进行 Java 编程,使用方式基本与使用 Java 代码的方式相同。在编写新应用程序时,Groovy 代码能够与 Java 代码很好地结合,...

    groovy-2.3.6-installer

    groovy-2.3.6-installer windows安装版本

    groovy-2.5.1-API文档-中文版.zip

    赠送jar包:groovy-2.5.1.jar; 赠送原API文档:groovy-2.5.1-javadoc.jar; 赠送源代码:groovy-2.5.1-sources.jar; 赠送Maven依赖信息文件:groovy-2.5.1.pom; 包含翻译后的API文档:groovy-2.5.1-javadoc-API...

    apache-groovy-sdk-2.4.11

    本文适合于不熟悉 Groovy,但想快速轻松地了解其基础知识的 Java开发人员。了解 Groovy 对 Java 语法的简化变形,学习 Groovy 的核心功能,例如本地集合、内置正则表达式和闭包。编写第一个 Groovy 类,然后学习如何...

    groovy-all-2.4.13-API文档-中文版.zip

    赠送jar包:groovy-all-2.4.13.jar; 赠送原API文档:groovy-all-2.4.13-javadoc.jar; 赠送源代码:groovy-all-2.4.13-sources.jar; 赠送Maven依赖信息文件:groovy-all-2.4.13.pom; 包含翻译后的API文档:groovy...

    groovy-all-2.4.5-API文档-中文版.zip

    赠送jar包:groovy-all-2.4.5.jar; 赠送原API文档:groovy-all-2.4.5-javadoc.jar; 赠送源代码:groovy-all-2.4.5-sources.jar; 赠送Maven依赖信息文件:groovy-all-2.4.5.pom; 包含翻译后的API文档:groovy-all...

    Eclipse Groovy插件

    Eclipse Groovy插件 Eclipse Groovy插件 Eclipse Groovy插件 Eclipse Groovy插件

    Groovy-3.0.jar

    Groovy jar包 3.0.

Global site tag (gtag.js) - Google Analytics