= GEP-11:Groovy 3 语义和新 MOP :icons: font .Metadata **** [horizontal,options="compact"] *编号*:: GEP-11 *标题*:: Groovy 3 语义和新 MOP *版本*:: 4 *类型*:: 功能 *状态*:: 草案 *负责人*:: Jochen "blackdrag" Theodorou *创建日期*:: 2012-06-26 *最后修改日期* :: 2018-10-28 **** == 摘要:Groovy 3 语义和新 MOP 一段时间以来,我们一直在考虑为 Groovy 提供一个新的 MOP,但尚未能确定最终设计,该设计主要保持向后兼容性,但也改进了我们希望改进的各种情况。在用户友好性和效率之间找到正确的路径是困难的。因此,新的 MOP 将包含一些旧的 MOP 所没有的功能,但也会为了更直接、更容易理解的 MOP 而移除一些旧功能。 == 原理 === 移除默认 null 参数 默认 null 参数用于具有一个参数的方法调用,但该参数的调用未提供参数。只要参数类型不是原始类型,Groovy 就会使用 null。如果是原始类型,则调用失败。此功能主要是在 Groovy 1.0 之前的早期年份添加的,当时 Groovy 不支持参数的默认值。此逻辑的另一个限制是它仅适用于单参数方法。因此,有许多用户对这种逻辑感到困惑。从性能上看,这种逻辑成本不高,当然也不会比带有实际参数的调用更高。但由于这是一个令人困惑且用途有限的功能,因此应将其移除。 === 移除自动列表展开 当使用列表进行方法调用时,如果找不到与该列表匹配的方法(例如,具有一个 List、Collection、Object 等类型参数的方法),将导致第二次方法选择迭代。此时,列表被“解包”,列表中的所有元素都被视为方法调用是使用这些元素而不是列表进行的。Groovy 还支持通过语法元素展开列表,这使得此自动功能不再需要。事实上,这可能会让用户感到非常惊讶,并且是性能方面的问题。展开版本在性能上可能仍然不佳,但至少用户必须使用额外的符号,因此会有视觉指示。至于此功能最初为何添加,目前尚不清楚。查看用户代码,您会发现几乎没有有意的用法。因此,应将其移除。 === 更改安全导航以停止评估 目前,如果 a 为 null,则诸如 a?.b.c 的表达式将失败。它不会评估 b,但会尝试在 null 上评估 c。这违背了安全导航避免 NullPointerException 的意图。因此,应将其更改为停止对路径表达式的评估。 === 用户心愿单 * Wujek Srujek 基于实例的类别 + 无需提供具有静态方法的类,最好能够将实例传递给 use 构造,然后我们将使用此实例及其实例方法。这允许使用实例状态。 * blackdrag 替换 Introspector + 不仅因为像 GROOVY-5019 这样的错误,还应该替换 Introspector === 此 MOP 的一些主要设计思想 计划是大量借鉴 JVM 提供的带 invokedynamic 的开放调用点缓存。为此,MOP 的所有部分都不应再被视为执行调用的地方,而是被视为返回某些东西的地方,然后该返回物将被调用。例如,invokeMethod 将返回一个充当某种处理程序的对象,该对象可以被调用。Groovy 将存储它并避免重新选择,除非您使其失效。在旧的 MOP 中,一旦您使用元编程进行交互,这种缓存通常就不会发生。要解决的任务是提供一个用于拦截方法的“扩展点”,并对缺失方法做出反应,以及能够使缓存版本失效和/或使未缓存版本成为可能。 === MOP2 概述 image:img/mop_2.jpg[image] x 的元类中的伪代码: ``` cachable = getMethodInterceptor(...) if (cachable==null) cachable = getFittingMethod(...) if (cachable==null) getMethodMissingHandler(...) if (cachable==null) throw new MethodMissingException(...) storeInCallSite(cachable) invoke(cachable) ``` 方法解释: * getMethodInterceptor + 用户注册拦截器的扩展点,将用于所有方法调用。这与 GroovyInterceptable 使用 invokeMethod 所做的事情相同,只是在 MOP1 中方法是直接调用的。在 MOP2 中,我们将返回一个处理程序,然后该处理程序将执行任何所需的操作。如果需要,这可以是一个指向 invokeMethod 方法的方法句柄。TODO:定义防护 * getFittingMethod + 通过元类选择方法的预定义方式。此方法将返回一个对象,然后该对象可用于调用所选方法。 * getMethodMissingHandler + 用户注册缺失方法处理程序的扩展点。然后调用的方法与 MOP1 中的 methodMissing 所做的事情类似。 * MethodMissingException + 如果上述方法均未产生非 null 结果,则抛出此异常 * storeInCallSite + 预定义方法,用于将上述过程的可缓存结果存储在调用点。只要缓存对象有效,此调用点将不再调用上述方法。 * invoke + 预定义方法,用于对可缓存对象进行初始调用。后续调用可由 invokedynamic 直接完成。 === 上下文元类 对于 MOP2,Groovy 将使用一个带有上下文元素和始终存在的默认值的元类系统。在每个调用点,只有一个这样的视图有效且它是常量。这些视图可用于定义“密封”元类,这些元类不受外部元类影响,或者例如允许调用私有方法而在其他情况下不允许。这使得目前有 3 种可能的元类变体: * 默认元类 + 其行为与 Java 在方法调用方面的允许方式非常相似(同一类的私有方法可用),所有元编程更改都可见。 * 密封元类 + 类似于默认元类,但来自类外部的元编程更改不可见。这对于库编写者特别有用。 * 测试元类(非密封) + 类似于前两种,但增加了私有成员始终可用。 === 获取元类 由于上下文的概念,类没有一个可以直接生成而无需其上下文的元类。调用点定义了上下文的位置。上下文本身如何定义是一个 TODO。作为一种实现策略,例如可以使用 ClassValue 来存储一个以上下文为键的表。键可能必须作为静态信息或易于计算的信息提供。由于生成的元类可以稍后存储在调用点对象中,因此应避免上下文更改,因为它意味着使用该上下文的调用点失效。 === 方法(和属性)的一般分派规则 为了正确定义分派规则,我们首先需要定义一些术语: + _静态发送类(SSC)_:这是调用源类的静态信息。例如,如果有一个类 A 和一个类 B 扩展 A,并且在 A 中的一个方法中进行调用,那么即使您的实例实际上是 B,SSC 仍然是 A。 + _基于继承的多方法_(从现在起简称多方法):给定一个类 A 和一个类 B 扩展 A,从 A 内部进行的调用可以看到 B 上定义的方法,只要该方法可见(非私有)。Groovy 对此定义了一个特殊例外。如果从 A 内部的方法调用正在调用名为 m 的方法,则只有当 A 中没有定义私有 m 时,B 中的 m 才能可见。给定这两个定义,A 中的方法调用将根据此选择方法集:一个 SSC 为 A 且在 B 实例(扩展 A)上进行的调用 m() 将使用 A 中定义的方法,如果 A 有一个私有 m,否则调用将使用 B。对 Super 的调用: + B 扩展 A 中对 super 的调用将具有 SSC B,但对于方法选择过程,将使用 SSC 的超类(super(SSC))。在 super 调用中,多方法不可见。因此,我们可以直接使用元类 super(SSC),但我们只分派该元类的公共方法。 === 模块扩展方法遮蔽规则 模块扩展方法在旧的和新的 MOP 中都由 DefaultGroovyMethods 相关类和模块扩展定义,例如 groovy-swing。在此定义中,我们将使用“内部”和“外部”术语来定义位于与目标方法相同的类中的调用点(内部)或不在同一类中的调用点(外部)。一般规则是: * 公共方法被遮蔽 * 私有方法对外部调用点遮蔽,但对内部调用点不遮蔽 应用了模块扩展方法的类的子类具有以下扩展规则: * 如果子类定义了一个与模块扩展方法具有相同签名的私有方法,则外部调用点仍将看到扩展方法,内部调用点将看到私有方法 * 对“super”或“this”的调用将调用模块扩展方法。因此,子类被视为外部调用点。开放块不被视为单独的类。 === 属性发现 目前,MetaClass 根据 Java Beans 约定发现属性。它还允许伪属性匹配 java.beans.EventSetDescriptor 上的约定。这允许在 SwingBuilder 中实现以下技巧,例如: ``` button(actionPerformed: { println it }) ``` 伪属性 actionPerformed 是从 ActionListener(一种可以在 JButton 上注册的监听器类型)公开的单个方法推断出来的。负责发现这些属性的代码隐藏在 MetaClassImpl 中,并且外部无法访问。如果此机制可以插件化,那将非常棒。 === 领域概念 在 MOP2 中,领域是包含元类的树状结构。有一个根领域,用作默认值,但可以有任意数量的下级领域。如果元类更改发生在同一领域内的元类或更高级别领域内的元类上,则该更改在该领域内可见。脚本执行引擎可以设置一个领域,例如,以防止它们更改不应更改的元类。这也可以用于单元测试,以隔离测试期间发生的元类更改。库可以有自己的领域(通过注解定义),以防止其他类将其更改泄漏到该库中,同时如果领域允许,库仍然可以使用更高级别的领域使更改更公开可见。领域可以有一个设置,防止从那里执行的代码对更高级别的领域进行更改。调用方法始终使用当前领域中的元类,即使被调用的类随后使用其自己的领域调用其他类。因此,领域不是线程局部结构,它更像是一种词法作用域。领域还可以使用不同的元类风格,例如允许访问私有方法和字段。 === 工作项 本部分旨在指导实施者采取行动并规划子任务。 * 将 indy 设为唯一的编译目标 * 将所有非 indy 字节码接口代码移动到一个模块中,该模块以后可能会被移除。这包括 ScriptBytecodeAdapter 以及所有自定义调用点缓存类 * 为 MOP2 创建一个新模块 * 将元类转换为不可变 * 实现元类视图 === 破坏性更改跟踪 `groovy.lang.MetaObjectProtocol`(目前在 `groovy.mop.MetaObjectProtocol` 中): * `getProperties()` 重命名为 `getMetaProperties()` * `getMethods()` 重命名为 `getMetaMethods()` * `respondsTo(Object, String, Object[])` 更改为 `respondsTo(String, Object...)` * `respondsTo(Object, String)` 替换为 `getMetaMethods(String, Class...)`,其中 class 参数为 null * `hasProperty(Object,String)` 替换为 `getMetaProperty(String)` 为 null 或不为 null * `getStaticMetaMethod(String, Object[])` 替换为 `respondsTo(String, Object...)` 并检查静态方法列表 * `getMetaMethod(name, Object[])` 替换为 `respondsTo(String, Object...)`,如果参数不是类,则替换为 `getMetaMethods(String,Class...)`,如果参数是类 * `invokeConstructor(Object[])` 无替换 * `invokeMethod(Object, String, Object[])` 无替换 * `invokeMethod(Object, String, Object)` 无替换 * `invokeStaticMethod(Object, String, Object[])` 无替换 * `getProperty(Object, String)` 替换为 `MetaProperty#invoke` * `setProperty(Object, String, Object)` 替换为 `MetaProperty#invoke` * `getAttribute(Object, String)` 替换为 `MetaProperty#getField#invoke` * `setAttribute(Object, String, Object)` 替换为 `MetaProperty#getField#invoke` * `getMetaProperty(String)`、`getTheClass()` 未更改 `groovy.lang.MetaMethod` 被拆分为公共接口 `groovy.mop.MetaMethod` 和内部默认实现 `groovy.mop.internal.DefaultMetaMethod`。与 `groovy.mop.internal.DefaultMetaMethod` 的区别: * 不再扩展 ParameterTypes 也不再实现 Clonable * 没有受保护字段 nativeParamTypes、parameterTypes 和 isVargsMethod * 构造函数 MetaMethod() 和 MetaMethod(Class[]) 被移除,部分替换为 DefaultMetaMethod(Class, String, int, MethodHandle) 和 DefaultMetaMethod(Class, String, MethodType),它们使用 MethodType 或 MethodHandle 来定义参数类 * `coerceArgumentsToClasses(Object[])`、`correctArguments(Object[])`、`isValidExactMethod(Class[])`、`isValidExactMethod(Object[])`、`isValidMethod(Class[])`、`isValidMethod(Object[])`、`isVargsMethod()`、`isVargsMethod(Object[])` 无替换 * `getNativeParameterTypes()` 替换为 `getParameterClasses()` * `equal(CachedClass[], CachedClass[])`、`equal(CachedClass[], Class[])`、`checkParameters(Class[])`、`clone()`、`doMethodInvoke(Object, Object[])`、`getDescriptor()` 无替换 * `getDeclaringClass()`、`getModifiers()`、`getName()`、`getReturnType()`、`isAbstract()`、`isPrivate()`、`isProtected()`、`isPublic()`、`isStatic()`、`toString()` 未更改 * `getMopName()`、`getSignature()`、`invoke(Object, Object[])`、`isCacheable()`、`isMethod(MetaMethod)`、`isSame(MetaMethod)`、`processDoMethodInvokeException(Exception, Object, Object[])` 无替换 `groovy.lang.MetaProperty` 被拆分为公共接口 `groovy.mop.MetaProperty` 和内部默认实现 `groovy.mop.internal.DefaultMetaProperty`。与 `groovy.mop.internal.DefaultMetaProperty` 的区别: * 公共静态字段 PROPERTY_SET_PREFIX 被移除,无替换 * 受保护字段 name 和 type 现在是私有的,必须通过 getName 和 getType 请求 * `getModifiers()`、`getName()`、`getType()`、`DefaultMetaProperty(String, Class)` 未更改 * `getGetterName(String, Class)`、`getSetterName(String)` 无替换 * `getProperty(Object)` 替换为 `getter(boolean)` * `setProperty(Object, Object)` 替换为 `setter(boolean)` == 参考和有用链接 * https://web.archive.org/web/20150508123746/http://docs.codehaus.org/display/GroovyJSR/GEP+11+-+Groovy+3+semantics+and+new+MOP[GEP-11: Groovy 3 语义和新 MOP](网页存档链接) === 邮件列表讨论 * https://marc.info/?l=groovy-user&m=13407096908201&w=2[groovy-user: Groovy 3] === 参考实现 * https://github.com/groovy/groovy-core/tree/GROOVY_3_FEATURE[GROOVY_3_FEATURE] + GitHub 上的功能分支 == 更新历史 3 (2013-10-11):: 从 Codehaus wiki 提取的版本 4 (2018-10-28):: 大量小幅调整