Gradle,让你放不下的一些语法

目录

Gradle中的属性和方法真的来自于Groovy中的Delegate吗?

Gradle不同于Groovy的特定语法


生活中的一些事,你耿耿于怀。他们说:你要懂得放下。于是你做了,忧伤没了,对“幸福”也有了认识。有一天,事又发生了,无论是亲身还是心里,你才知道这叫“选择性忘记”。

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的基础上显得有点标新立异。

请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值