目录
Gradle中的属性和方法真的来自于Groovy中的Delegate吗?
生活中的一些事,你耿耿于怀。他们说:你要懂得放下。于是你做了,忧伤没了,对“幸福”也有了认识。有一天,事又发生了,无论是亲身还是心里,你才知道这叫“选择性忘记”。
Gradle是在软件开发过程中管理软件项目的一个工具类软件,其功能主要围绕软件项目的开发和管理。
Gradle中的属性和方法真的来自于Groovy中的Delegate吗?
我们带着Gradle based on Groovy的概念初次接触gradle的api,源于对groovy闭包的认识,“没错!是一个大闭包,它的代理对象是Gradle,Settings,Project!”我还真不错,gradle文件中方法和属性的调用原理都懂!你带着这个认知上路了很久。
直到有一天你笑了一下,“大闭包??!!”,真的是这样设计的吗?于是你开始放不下了......,qtmd的大闭包。
一则实例的尾随跟踪。
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.7'
compile 'org.scala-lang:scala-library:2.11.1'
}
我把鼠标悬停在这段api上思忖良久,一不留神视界被带进了Project接口的如下方法:
void dependencies(Closure configureClosure);
没错,它调用的就是“代理”Project的dependencies 方法,该方法接收一个闭包参数,该闭包的delegate,owner,this都是指向Project的实现对象。恩,好,接下来,我再次主动把视界投向Project接口的compile方法,但我发现,我被带进了一个叫DependencyHandler接口的如下方法:
Dependency add(String configurationName, Object dependencyNotation);
说好的Project代理呢?我像个小孩一样哭闹了起来,你TM怎么和我当初的人设不一样了,Gradle开始令我讨厌起来。
我带着复杂的心情,和她真正接触了一下,才知道她不叫“小芳”,而叫“翠花”,会做酸菜。我带着复杂的心情,找到了gradle文件对应的class文件,它叫Script,会把gradle文件中的方法和属性调用转换成class文件中的Callsite,会把gradle文件中的“{}”语法翻译成Closure的子类,且可嵌套,“小芳”中的顶级Closure的delegate,owner,this是Script子类对象。
接下里看一下,groovy编译器对本例的翻译片段:
callsite.name = "dependencies"
callsite.callCurrent(this, new _run_closure(this, this));
继续跟踪这段代码,发现Script把调用责任推卸给了自己的MetaClass,我把它推卸责任的罪证呈堂一下:
groovy.lang.MetaClassImpl@19a64eae[class Script] metaClass
Script receiver
String name = "dependencies"
Object[] args = new Object[]{new Script$_run_closure}
metaClass.invokeMethod(receiver, name, args);
MetaClass一圈的调用逻辑(是Groovy的干的事,在此略过)最终会反射调用MetaClass的宿主类也就是Script的methodMissing方法:
public Object methodMissing(String name, Object params) {
return getDynamicTarget().invokeMethod(name, (Object[])params);
}
其中getDynamicTarget()返回的是Project接口实现类的动态对象(ExtensibleDynamicObject),它实现了DynamicObject接口。
动态对象是Gradle的一项核心设计,它把gradleApi中的方法调用和属性获取工作全部推卸给动态对象。关于动态对象的设计接口有如下:
public interface DynamicObjectAware {
DynamicObject getAsDynamicObject();
}
public interface ExtensionAware {
ExtensionContainer getExtensions();
}
public interface IConventionAware {
ConventionMapping getConventionMapping();
}
public interface HasConvention {
Convention getConvention();
}
public interface DynamicObject {
/**
* Creates a {@link MissingPropertyException} for getting an unknown property of this object.
*/
MissingPropertyException getMissingProperty(String name);
/**
* Creates a {@link MissingPropertyException} for setting an unknown property of this object.
*/
MissingPropertyException setMissingProperty(String name);
/**
* Creates a {@link MissingMethodException} for invoking an unknown method on this object.
*/
MissingMethodException methodMissingException(String name, Object... params);
/**
* Returns true when this object is known to have the given property.
*
* <p>Note that not every property is known. Some properties require an attempt to get or set their value before they are discovered.</p>
*/
boolean hasProperty(String name);
/**
* Gets the value of the given property, attaching it to the given result using {@link GetPropertyResult#result(Object)}.
*
* <p>Use the {@link GetPropertyResult#isFound()} method to determine whether the property has been found or not.</p>
*/
void getProperty(String name, GetPropertyResult result);
/**
* Don't use this method. Use the overload above instead.
*/
Object getProperty(String name) throws MissingPropertyException;
/**
* Sets the value of the given property. The implementation should call {@link SetPropertyResult#found()} when the property value has been set.
*
* <p>Use the {@link SetPropertyResult#isFound()} method to determine whether the property has been found or not.</p>
*/
void setProperty(String name, Object value, SetPropertyResult result);
/**
* Don't use this method. Use the overload above instead.
*/
void setProperty(String name, Object value) throws MissingPropertyException;
Map<String, ?> getProperties();
/**
* Returns true when this object is known to have a method with the given name that accepts the given arguments.
*
* <p>Note that not every method is known. Some methods are require an attempt to get or set its value.</p>
*/
boolean hasMethod(String name, Object... arguments);
/**
* Invokes the method with the given name and arguments.
*/
void invokeMethod(String name, InvokeMethodResult result, Object... arguments);
/**
* Don't use this method. Use the overload above instead.
*/
Object invokeMethod(String name, Object... arguments) throws MissingMethodException;
}
最终,ExtensibleDynamicObject在Project的实现类上找到了 如下方法:
public void dependencies(Closure configureClosure) {
ConfigureUtil.configure(configureClosure, getDependencies());
}
“小芳”打开了心扉,她不是大闭包,也不是delegate,她是DynamicObject,我内心独白:你为人真复杂!她接着说:我都这样了,你还喜欢我吗?我上演了姚明的表情包,不由自主地道:喜欢,但是你得继续打开心扉。
getDependencies() 返回的是一个DependencyHandler接口类型的实现类对象,和上次反我人设的哭闹不磨而合,我开始放下了那一丝厌恶,继续跟踪下去。
public static <T> T configure(@Nullable Closure configureClosure, T target) {
if (configureClosure == null) {
return target;
}
if (target instanceof Configurable) {
((Configurable) target).configure(configureClosure);
} else {
configureTarget(configureClosure, target, new ConfigureDelegate(configureClosure, target));
}
return target;
}
private static <T> void configureTarget(Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
if (!(configureClosure instanceof GeneratedClosure)) {
new ClosureBackedAction<T>(configureClosure, Closure.DELEGATE_FIRST, false).execute(target);
return;
}
// Hackery to make closure execution faster, by short-circuiting the expensive property and method lookup on Closure
Closure withNewOwner = configureClosure.rehydrate(target, closureDelegate, configureClosure.getThisObject());
new ClosureBackedAction<T>(withNewOwner, Closure.OWNER_ONLY, false).execute(target);
}
public void execute(T delegate) {
if (closure == null) {
return;
}
try {
if (configureableAware && delegate instanceof Configurable) {
((Configurable) delegate).configure(closure);
} else {
Closure copy = (Closure) closure.clone();//copy就是上面的withNewOwner的副本
copy.setResolveStrategy(resolveStrategy);//就是上面的Closure.OWNER_ONLY
copy.setDelegate(delegate); //
if (copy.getMaximumNumberOfParameters() == 0) {//doCall的参数个数是否等于0
copy.call();
} else {
copy.call(delegate); //反射调用闭包的doCall方法,调用的闭包类是脚本中的某一个groovy编译器为我们生成好的Closure的子类,例如:_run_closure2
}//这个例子中我们没有用到delegate参数,即groovy为我们生成的DefaultDependencyHandler的子类
}
} catch (groovy.lang.MissingMethodException e) {
if (Objects.equal(e.getType(), closure.getClass()) && Objects.equal(e.getMethod(), "doCall")) {
throw new InvalidActionClosureException(closure, delegate);
}
throw e;
}
}
我有跟踪到了闭包_run_closure的doCall方法 :
public Object doCall(Object it) {
CallSite acallsite1[] = $getCallSiteArray();
try {
//acallsite1[0] = "compile"
acallsite1[0].callCurrent(this, "org.codehaus.groovy:groovy-all:2.4.7");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
try {
//acallsite1[1] = "compile"
return acallsite1[1].callCurrent(this, "org.scala-lang:scala-library:2.11.1");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
闭包中CallSite把调用责任推卸给了自己的MetaClass,我把它推卸责任的罪证呈堂一下:
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass@7c847072[class Script$_run_closure2] metaClass
Script$_run_closure receiver
String name = "compile"
Object[] args = new Object[]{"org.codehaus.groovy:groovy-all:2.4.7"}
metaClass.invokeMethod(receiver, name, args);
ClosureMetaClass一圈的调用逻辑(是Groovy的干的事,在此略过)会调用如下:
/**invokeOnOwner = true;
owner就是上面ConfigureDelegate类型的参数;
invokeOnDelegate = fasle;
delegate就是上面闭包的代理设置,它是DefaultDependencyHandler类型
methodName = "compile"
Object[] arguments = new Object[]{"org.codehaus.groovy:groovy-all:2.4.7"}
**/
invokeOnDelegationObjects(invokeOnOwner, owner, invokeOnDelegate, delegate, methodName, arguments){
......
GroovyObject go = (GroovyObject) owner;
try {
return go.invokeMethod(methodName, args);
}catch (MissingMethodException mme) {
......
}
......
}
ConfigureDelegat是GroovyObject的子类所以实现了public Object invokeMethod(String name, Object paramsObj)方法,上面的代码可知ConfigureDelegate包装有target,也就是DependencyHandler类型的子类实现,由gradle的类生成器动态地为我们生成 。
ConfigureDelegat一上来就走DefaultDependencyHandler实现类的动态对象接口,即通过其getAsDynamicObject方法获取动态对象,并在其动态对象身上查找方法,其动态对象是一个复合对象(CompositeDynamicObject类型),也就是复合多个动态对象,因此逐一遍历查找,其中一个的查找就调用至如下:
/**
metaClass : groovy.lang.MetaClassImpl@f6de586[class org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler_Decorated]
bean : DefaultDependencyHandler_Decorated
name = "compile"
Object[] arguments = new Object[]{"org.codehaus.groovy:groovy-all:2.4.7"}**/
protected Object invokeOpaqueMethod(MetaClass metaClass, String name, Object[] arguments) {
return metaClass.invokeMethod(bean, name, arguments);
}
上面其实开启了和查找"dependencies"方法一样的流程去查找“compile”方法,在次略过。最终会反射调用MetaClass的宿主类也就是DefaultDependencyHandler_Decorated类(gradle的类生成器动态生成,继承自DefaultDependencyHandler)的methodMissing方法。
public Object methodMissing(String name, Object args) {
Object[] argsArray = (Object[]) args;
Configuration configuration = configurationContainer.findByName(name);
if (configuration == null) {
throw new MissingMethodException(name, this.getClass(), argsArray);
}
List<?> normalizedArgs = CollectionUtils.flattenCollections(argsArray);
if (normalizedArgs.size() == 2 && normalizedArgs.get(1) instanceof Closure) {
return doAdd(configuration, normalizedArgs.get(0), (Closure) normalizedArgs.get(1));
} else if (normalizedArgs.size() == 1) {
return doAdd(configuration, normalizedArgs.get(0), null);
} else {
for (Object arg : normalizedArgs) {
doAdd(configuration, arg, null);
}
return null;
}
}
private Dependency doAdd(Configuration configuration, Object dependencyNotation, Closure configureClosure) {
if (dependencyNotation instanceof Configuration) {
Configuration other = (Configuration) dependencyNotation;
if (!configurationContainer.contains(other)) {
throw new UnsupportedOperationException("Currently you can only declare dependencies on configurations from the same project.");
}
configuration.extendsFrom(other);
return null;
}
Dependency dependency = create(dependencyNotation, configureClosure);
configuration.getDependencies().add(dependency);
return dependency;
}
到这里,我说了句:“小芳”你好!
Gradle不同于Groovy的特定语法
groovy方法的调用方式,在我前面的文章因为见到过一些,点击这里。但是如下的写法,你见过吗?
task testTask {
doLast {
println it.name
}
}
外暴内柔的老哥说这就是groovy中的方法调用,它调用了Project的方法Task task(String name, Closure configureClosure);省略了圆括号,事实如此吗?是不是你脑补了逗号或者裸奔的字符串?
请记住,每一个华丽的“小芳”背后都有一个“翠花”在死撑。
我找到了“翠花”,拉着她粗糙的双手,和她攀起了家常,她说她其实长这样:
//as[0] = "task";
acallsite[0].callCurrent(this, "testTask", new _run_closure1(this));
我说:哪你让这样的“小芳”们,如何存在:
task "testTask" ,{
doLast {
println it.name
}
}
task ("testTask"){
doLast {
println it.name
}
}
task("testTask",{
doLast {
println it.name
}
})
她说:现在的科技发达,狗和人都可以互相转换。我顿时醒悟了,她说的是: Groovy的ASTTransformer。她能把一棵AST Tree转换成另一棵AST Tree,然后 Groovy编译器在狗或者人成为人或者狗之前根据AST Tree整型了狗或者人。Gradle实现了一个ASTTransformer,她把各种华丽的"小芳"转换到统一的“翠花”身上。
也就是,ASTTransformer会把task <identifier> <closure>文法映射到task("<identifier>", <arg-list>) | task(<string>, <arg-list>)文法,从而让Gradle在Groovy的基础上显得有点标新立异。
请注明出处。