2021SC@SDUSC
9、Groovy闭包源码分析
闭包是什么
Groovy 中闭包是最有用的特性之一,也是非常重要的特性,可以说 Groovy 精髓都围绕闭包。
- 一个闭包是被包装为一个对象实例的代码块,实际上闭包像一个可以接受参数并且能够有返回值的方法。
- 闭包是一个普通对象,因为你能够通过一个变量引用到它,正如你能够引用到任何别的对象一样。
- 基于 Groovy 语言本质介绍可以知道,JVM 根本就不知道你正在运行 Groovy 代码,所以 Groovy 闭包是一个普通对象是很自然的现象。
- Groovy 在集合对象上增加了许多额外的方法(each、 find、findAll、collect 等),使用闭包来指定这些每次都被执行的代码块会变得容易且直观。
反编译闭包实例
下面我们来反编译一个闭包实例来分析一下源码逻辑,因为前面我们说过,Groovy 本质就是 JVM 语言,其编译后也是 class,只是 classPath 比 JDK 多了 GDK 而已:
//test.groovy
def testStr = ''
(1..10).each({
testStr += it
})
print(testStr)
如上代码使用groovyc test.groovy编译产物为test.class和test$_run_closure1.class,_run_closure1 为 test 的内部类名
(只列出有关闭包的核心代码):
/**
* 重点!!!
* 入口代码
*/
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].call(InvokerHelper.class, test.class, args);
}//之前博客所解释的反射和动态方法执行优化
public Object run() {
CallSite[] var1 = $getCallSiteArray();
//这就是我们定义的 testStr 接收字符串变量
final Reference testStr = new Reference("");
/**
* 闭包被转换为继承 Closure 的普通 java 类
*/
final class _run_closure1 extends Closure implements GeneratedClosure {
public _run_closure1(Object _outerInstance, Object _thisObject) {
CallSite[] var4 = $getCallSiteArray();
super(_outerInstance, _thisObject);
}
//闭包的调用本质调用的是这个方法,所有下面对此进行源码查看并解释
public Object doCall(Object it) {
CallSite[] var2 = $getCallSiteArray();
Object var10000 = var2[0].call(testStr.get(), it);
testStr.set(var10000);
return var10000;
}
public Object getTestStr() {
CallSite[] var1 = $getCallSiteArray();
return testStr.get();
}
@Generated
public Object doCall() {
CallSite[] var1 = $getCallSiteArray();
return this.doCall((Object)null);
}
}
//range的翻译
var1[1].call(ScriptBytecodeAdapter.createRange(1, 10, (boolean)1), new _run_closure1(this, this));
return var1[2].callCurrent(this, testStr.get());
}
}
Groovy 的一切都是对象,通过上面源码查看我们可以知道闭包的本质就是 Closure 实例对象,而闭包调用的实质则是调用了docall方法。而闭包的创建,就是生产了一个继承于Closure的类,并且重写了其中方法形成闭包。上面的代码除了动态方法的调用外和一些形如range的java翻译外,关于闭包的重点应该都在Closure里面,下面就让我进入Closure进行源码分析。
Closure源码分析
在分析Closure之前,我们需要明确一下Closure的重要特色——闭包的委托策略。
闭包的三个重要变量:this,owner,delegate
区别在于:this代表闭包定义处最近的对象(不包含闭包),owner代表闭包定义出最近的对象可以是闭包.delegate默认与owner一致,但delegate可以修改。通过这三个变量可以委派方法变量使得闭包更加灵活。
下面进行闭包源码分析:
public abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable {
//可见每个闭包对象实例内部都有一个委托对象属性
private Object delegate;
// 可见每个闭包对象实例内部都有一个owner对象属性
private Object owner;
// 可见每个闭包对象实例内部都有一个this对象属性
private Object thisObject;
/**
* 委托策略定义,默认是OWNER_FIRST
*/
private int resolveStrategy = OWNER_FIRST;
private int directive;
/**
* 闭包参数类型和个数相关属性
*/
protected Class[] parameterTypes;
protected int maximumNumberOfParameters;
private static final long serialVersionUID = 4368710879820278874L;
private BooleanClosureWrapper bcw;
/**
* 闭包构造方法,每个括号声明的闭包实例背后都是通过构造方法实例化的
*/
public Closure(Object owner, Object thisObject) {
this.owner = owner;
this.delegate = owner;
this.thisObject = thisObject;
final CachedClosureClass cachedClass = (CachedClosureClass) ReflectionCache.getCachedClass(getClass());
parameterTypes = cachedClass.getParameterTypes();
maximumNumberOfParameters = cachedClass.getMaximumNumberOfParameters();
}
/**
* 熟悉的闭包内部 this 获取方法
*/
public Object getThisObject(){
return thisObject;
}
/**
* 闭包的调用方法 call,可以传递参数
*/
public V call(Object... args) {
try {
return (V) getMetaClass().invokeMethod(this,"doCall",args);
} catch (InvokerInvocationException e) {
ExceptionUtils.sneakyThrow(e.getCause());
return null; // unreachable statement
} catch (Exception e) {
return (V) throwRuntimeException(e);
}
}
......
/**
* 获取闭包内部的 owner 和 delegate
*/
public Object getOwner() {
return this.owner;
}
public Object getDelegate() {
return this.delegate;
}
public void setDelegate(Object delegate) {
this.delegate = delegate;
}
/**
* 依赖闭包是接受一个参数还是多个参数,通过闭包的这个方法能获取到期望的参数数量信息(类型,如果声明了类型)。
* 譬如调用闭包的前面先 closure.getParameterTypes().size() 获取参数个数再决定怎么调用等。
*/
public Class[] getParameterTypes() {
return parameterTypes;
}
/**
* 返回一个当前闭包的克隆版,这个克隆版有一个附加的 writeTo(Writer) 方法来直接将闭包的结果输出到给定的 Writer 中。
*/
public Closure asWritable() {
return new WritableClosure();
}
/**
* 执行闭包代码,本质是调用了闭包的 call 方法
*/
public void run() {
call();
}
/**
* Closure 的 curry 方法返回当前闭包的一个克隆品,这个克隆品已经绑定 了一个或者多个给定的参数,参数的绑定是从左向右进行的。
* def sum = {x, y => return x + y }
* def sumOne = sum.curry(1)
* assert sumOne(5) == 6
* Curry 最强大的地方是当闭包的参数是闭包本身的时候,通常在函数式编程时使用到。
*/
public Closure<V> curry(final Object... arguments) {
return new CurriedClosure<V>(this, arguments);
}
......
}
到此算是把 Groovy 的闭包扒了一层皮,完全摸透了,从表象到内部机制,对你日常使用应该是没问题了。搞懂了本质原理,剩下的那些方法基本就是纯 API 用法了,这里不再展开。