用于映射>类绑定的时髦 AS 关键字



给定以下类:

class A {
B b
int data
int data2
}
class B {
C c
String data
}
class C {
Date data
}

代码工作正常:

Date now = new Date()
def a = [ data:42, data2:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
assert a.b.c.data == now
assert a.data == 42
assert a.data2 == 84

现在,如果我省略data2:84,代码仍然可以正常工作,当然除了最后assert

但!如果我"拼错"属性名称,例如:

def a = [ data:42, data22:84, b:[ data:'BBB', c:[ data:now ] ] ] as A

我得到了

java.lang.NullPointerException:无法在空对象上获取属性"c">

我看到的是,只有A类被实例化为 no-arg 构造函数,bc属性都是nulls。

所以,跳过!=拼写错误。

因此,有两个问题:

  1. (而是系统生理(。 这是预期的行为吗?不应该跳过拼写错误的道具吗?

  2. 如何让as关键词"宽大"跳过未知道具?

蒂亚

更新:

创建 JIRA 任务 https://issues.apache.org/jira/browse/GROOVY-9348

有一个主要区别。使用包含现有类字段的映射构造函数时,将初始化常规A对象。这是println a.dump()在这种情况下产生的结果。

<A@7bab3f1a b=B@1e1a0406 data=42 data2=84>

但是,如果将不由类字段表示的条目放入映射中,则 Groovy 不会初始化A对象,而是创建A类的代理。

<A1_groovyProxy@537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:39:50 CET 2019]]] b=null data=0 data2=0>

此代理根本不初始化字段,但它将使用构造函数传递的映射存储为内部$closures$delegate$map字段。

看看我用你的例子做的以下分析。

  • DefaultGroovyMethods.asType(Map map, Class<?> clazz)抛出内部GroovyCastException

    org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{data=42, data22=84, b={data=BBB, c={data=Fri Dec 20 12:22:28 CET 2019}}}' with class 'java.util.LinkedHashMap' to class 'A' due to: org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: No such property: data22 for class: A
    Possible solutions: data2, data
    

    来源:https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11813

  • 捕获异常并调用回退方法:

    return (T) ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(map, clazz);
    

    来源:https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11816

  • ProxyGenerator使用以下方法初始化ProxyGeneratorAdapter

    public GroovyObject instantiateAggregate(Map closureMap, List<Class> interfaces, Class clazz, Object[] constructorArgs) {
    if (clazz != null && Modifier.isFinal(clazz.getModifiers())) {
    throw new GroovyCastException("Cannot coerce a map to class " + clazz.getName() + " because it is a final class");
    }
    Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP;
    ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, null, clazz);
    return adapter.proxy(map, constructorArgs);
    }
    

    来源:https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L162

    此方法可能存在错误:在createAdapter(map, interfaces, null, clazz)部分中,null值表示delegatingClass对象。当它为 null 时,生成的代理类中没有应用委派机制。

  • 最后但并非最不重要的一点是,adapter.proxy(map, constructorArgs)实例化一个新对象,其字符串转储表示形式如下所示:

    <A1_groovyProxy@537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:29:06 CET 2019]]] b=null data=0 data2=0>
    

    如您所见,传递给构造函数的映射存储为$closure$delegate$map字段。所有A类值都使用其默认值进行初始化(b字段为null,其余 int 字段为0(。

  • 现在,ProxyGenerator类有一个创建支持委派的适配器的方法:

    public GroovyObject instantiateDelegateWithBaseClass(Map closureMap, List<Class> interfaces, Object delegate, Class baseClass, String name) {
    Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP;
    ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, delegate.getClass(), baseClass);
    return adapter.delegatingProxy(delegate, map, (Object[])null);
    }
    

    来源: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L203

    我猜也许如果使用具有非空delegatingClassProxyGeneratorAdapter,调用a.b将使用内部委托映射中的值,而不是b字段值。这只是我的假设。

问题是:这是一个错误吗?这要看情况。正如 cfrick 在其中一条评论中提到的,使用不正确的映射初始化A会引发明确的错误,您就完成了。在这里,此异常被抑制,从调用者的角度来看,您不知道后台发生了什么。我使用 Groovy 2.5.8 和 3.0.0-RC1 运行这些测试,这两个版本中的行为相同。在Apache Groovy JIRA项目中报告这是一个问题听起来很合理,所以你可以从Groovy核心维护者那里得到反馈。