一、MOP
前面一节说过build.gradle 脚本会被编译为一个ProjectScript 的子类,因此我们可以在build.gradle 里面调用ProjectScript的所有方法,但是上面调用的getRootProject 方法是Project 的方法,难道build.gradle脚本编译之后生成的类是Project 的子类?。答案当然是否定的。下面我们就来看看这是怎么回事。
1.1 groovy 对象
首先我们要知道Gradle 脚本使用的groovy 语言。
Groovy 中的对象其实本质也是 Java 对象,只不过比 Java 对象附加了一些其它的功能。在 Groovy 中的对象,其顶级父类也是 java.lang.Object,同时其也实现了 groovy.lang.GroovyObject 接口。
每一个groovy 类都有一个MetaClass 成员。 该 MetaClass 对象持有其所依附的对象的所有信息(包括属性和方法),每当我们调用一个对象的方法时,都是由该 MetaClass 对象负责路由对方法的调用。我们知道一旦一个类被加载进 JVM,那么这个类就无法修改了,但是我们可以修改这个类的 MetaClass 对象,从而实现对类动态的添加方法和行为。
该图描述了 Groovy 中方法调用的路由机制。这里做以下补充:
- invokeMethod 方法是 GroovyObject 接口中的方法,所有的 Groovy 类都默认实现了该方法。而
GroovyInterceptable 只是一个标记接口,该接口的作用是将 invokeMethod
方法的调用时机提前到了最前面,也就是所有的方法调用都会先统一路由到 invokeMethod 方法中,若为实现
GroovyInterceptable 接口,那么 invokeMethod 方法只有最后才有机会执行。 - 若在类的定义中声明了 GroovyInterceptable 接口,但是在类中没有覆盖 invokeMethod 方法,则等同于没有实现
GroovyInterceptable 接口,路由转向左侧。 - 若未实现 GroovyInterceptable 接口,而一个类的外部直接调用了 invokeMethod
方法,那么就是方法的直接调用了,不存在拦不拦截的问题,但是如果该类中又没有覆盖 invokeMethod 方法,那么会调用methodMissing 方法(如果有的话) - 若向一个类的 metaClass 中添加了 invokeMethod 方法或者 methodMissing
方法,在外部调用一个不存在的方法时,会路由到该 invokeMethod 方法上,如果没有实现 invokeMethod
方法,那么会路由到 metaClass 上的 methodMissing 方法上(如果有的话)
Gradle 对于方法的路由进行了扩充。前面说了build.gradle 脚本会被编译为BaseScript的子类。而扩充的实现就在BaseScript 类里面。
public abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations, ProcessOperations, DynamicObjectAware {
private StandardOutputCapture standardOutputCapture;
private Object target;
private ScriptDynamicObject dynamicObject = new ScriptDynamicObject(this);
public Object getScriptTarget() {
return target;
}
//这个放在在脚本编译完成之后有gradle 系统调用
private void setScriptTarget(Object target) {
// target 可以看成是方法的实现者。
this.target = target;
this.dynamicObject.setTarget(target);
}
@Override
public Object invokeMethod(String name, Object args) {
return dynamicObject.invokeMethod(name, (Object[]) args);
}
private static final class ScriptDynamicObject extends AbstractDynamicObject {
private final DynamicObject scriptObject;
private DynamicObject dynamicTarget;
ScriptDynamicObject(org.gradle.groovy.scripts.BasicScript script) {
this.binding = script.getBinding();
scriptObject = new BeanDynamicObject(script).withNotImplementsMissing();
dynamicTarget = scriptObject;
}
public void setTarget(Object target) {
//target 是真正实现方法的类
dynamicTarget = DynamicObjectUtil.asDynamicObject(target);
}
@Override
public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) {
//scriptObject 代表的就是脚本文件
DynamicInvokeResult result = scriptObject.tryInvokeMethod(name, arguments);
if (result.isFound()) {
return result;
}
//调用 dynamicTarget 的tryInvokeMethod
return dynamicTarget.tryInvokeMethod(name, arguments);
}
@Override
public Object invokeMethod(String name, Object... arguments) throws
groovy.lang.MissingMethodException {
DynamicInvokeResult result = tryInvokeMethod(name, arguments);
if (result.isFound()) {
return result.getValue();
}
throw methodMissingException(name, arguments);
}
}
}
BasicScript 定义了invokeMethod 方法,也就是在调用BasicScript 类里面不存在的方法的时候MetaCalss 会调用到invokeMethod 方法,同时将调用方法的名字与调用方法是传递的参数传递给invokeMethod 方法。BasicScript 的invokeMethod 又调用了dynamicObject的tryInvokeMethod 方法,参数自然就是调用的方法的名字与参数。
ScriptDynamicObject 的tryInvokeMethod 首先在scriptObject(就是脚本文件BasicScript) 里面查找是否存在方法,如果不存在就在dynamicTarget 里面查找。dynamicTarget 可以看成是真正实现被调用方法的代理类。
public abstract class DynamicObjectUtil {
public static DynamicObject asDynamicObject(Object object) {
if (object instanceof DynamicObject) {
return (DynamicObject)object;
} else if (object instanceof DynamicObjectAware) {
return ((DynamicObjectAware) object).getAsDynamicObject();
} else {
return new BeanDynamicObject(object);
}
}
}
如果object 实现了DynamicObjectAware接口,就调用DynamicObjectAware 的getAsDynamicObject 方法,否则返回BeanDynamicObject,BeanDynamicObject 是一个代理类。
现在的问题是这个object 是什么类型的呢?
getScriptTarget().class
大家可以在脚本里面通过getScriptTarget().class 获取到这个类。
class org.gradle.api.internal.project.DefaultProject_Decorated
得到的是DefaultProject_Decorated,这是DefaultProject的子类。
DefaultProject 实现了Project与DynamicObjectAware 接口。所以asDynamicObject 返回的是DefaultProject的getAsDynamicObject 返回值。具体的细节我们放到下一章节再说。
在编译setting.gradle 脚本的时候,会根据配置创建对应的Project。每一个Project 对应一个build.gradle 脚本。在编译完这个脚本会生成一个BaseScript的子类,之后调用BaseScript 的setTarget 保存对应的Project 对象。
到此我们先总结一下,当调用一个方法的时候执行过程。
1 如果该方法定义在脚本里面,就执行这个方法。
2 如果方法没有定义在脚本里面,调用脚本的invokeMethod 方法
3 脚本的invokeMethod 调用了ScriptDynamicObject的 invokeMethod,进而调用到ScriptDynamicObject的tryInvokeMethod
4 ScriptDynamicObject的tryInvokeMethod 调用scriptObject的tryInvokeMethod 。scriptObject为BeanDynamicObject,可以看成是脚本的代理,BeanDynamicObject 的tryInvokeMethod 会检查被代理的对象有没有有实现MethodMixIn 接口,如果实现了该接口那么特殊处理,实际上BasicScript 没有实现该接口。如果还是没找到被调用的方法进入下一步。
5 调用dynamicTarget 的tryInvokeMethod 方法。dynamicTarget 可看成是实现对应方法的代理类。
总结
到此我们就知道了,当调用一个在build.gradle 脚本里面没有定义的方法的时候,此时会调用DefaultProject 的getAsDynamicObject 方法。然后调用其返回对象的tryInvokeMethod 方法在DefaultProject 里面查找被调用的方法。查找的过程比较复杂,因此放到下一章节里面介绍。