【问题标题】:Override a method already added via expandoMetaClass覆盖已通过 expandoMetaClass 添加的方法
【发布时间】:2016-10-07 13:47:14
【问题描述】:

我无法理解 GroovyClassLoader 缓存如何与 expando 方法一起使用。

特别是,我希望以下代码为 Number.test() 的每次调用提供不同的输出,但事实并非如此。

int i = 1

Number.metaClass.test = {
  return "A string"
}
println i.test()

println GroovySystem.metaClassRegistry.getMetaClass(Number.class)
Number.metaClass = null
println GroovySystem.metaClassRegistry.getMetaClass(Number.class)

Number.metaClass.test = {
  return "Another String"
}
println i.test()

println GroovySystem.metaClassRegistry.getMetaClass(Number.class)
GroovySystem.metaClassRegistry.removeMetaClass(Number.class)
println GroovySystem.metaClassRegistry.getMetaClass(Number.class)

Number.metaClass.test = {
  return "Yet another String"
}
println i.test()

输出:

A string
groovy.lang.ExpandoMetaClass@3571b748[class java.lang.Number]
groovy.lang.MetaClassImpl@7748410a[class java.lang.Number]
A string
groovy.lang.ExpandoMetaClass@37f1104d[class java.lang.Number]
groovy.lang.MetaClassImpl@55740540[class java.lang.Number]
A string

编辑:

我尝试了更多的东西。看起来我的系统表现得很奇怪。给定以下代码:

def i = 1

i.class

Number.metaClass.test = {
  return "A string"
}
println i.test()

Number.metaClass = null
Number.metaClass.test = {
  return "Another String"
}
println i.test()

GroovySystem.metaClassRegistry.removeMetaClass(Number.class)
Number.metaClass.test = {
  return "Yet another String"
}
println i.test()

当我在我的系统上执行它时,它会按预期工作(在输出中获得 3 个不同的字符串),但如果我评论了所谓的无用 i.class,它会失败(3 x A string)。

但是,如果我在 groovy-console.appspot.com 上执行它,无论有没有该行,它都会给我预期的输出...

我有点不知所措。

【问题讨论】:

    标签: groovy metaprogramming


    【解决方案1】:

    当您创建int i = 9 时,它使用当前Number.metaClass 创建了一个对象。它接受创建一个新方法(阅读Number.metaClass.test = { "A string" }),但不允许重载已经存在的方法。您可以改为更改对象的元类:

    int i = 1
    
    Number.metaClass.test = { "A string" }
    assert i.test() == "A string"
    
    Number.metaClass = null
    
    i.metaClass.test = { "Another String" }
    assert i.test() == "Another String"
    
    GroovySystem.metaClassRegistry.removeMetaClass(Number.class)
    assert i.test() == "Another String"
    
    i.metaClass.test = { "Yet another String" }
    assert i.test() == "Yet another String"
    

    需要调用i.class 对我来说似乎是一个错误......看看jira,我看到了一些未解决和打开的错误。我认为你的看起来和这些相似:

    在实例 metaClass 更改之前,Java 类 metaClass 上的覆盖方法不会生效:https://issues.apache.org/jira/browse/GROOVY-5065

    使用 metaClass 覆盖类层次结构中的方法无法按预期工作:https://issues.apache.org/jira/browse/GROOVY-3942

    通过 .metaClass 覆盖方法的行为不一致:https://issues.apache.org/jira/browse/GROOVY-6847

    删除一个元类方法:https://issues.apache.org/jira/browse/GROOVY-4189


    如果您考虑一个具体的类,我的答案的第一部分有效:

    class Echo {}
    Echo.metaClass.test = { "A string" }
    def i = new Echo()
    assert i.test() == "A string"
    
    Echo.metaClass = null
    Echo.metaClass.test = { "Another String" }
    assert i.test() == "A string"
    
    i.metaClass.test = { "Another String" }
    assert i.test() == "Another String"
    
    i.metaClass.test = { "Yet another String" }
    assert i.test() == "Yet another String"
    

    【讨论】:

    • 感谢您的回答,它促使我进行更多调查,但正如您在我的编辑中看到的那样,我不确定解释是否像您的那样简单。
    • 再次感谢。我见过 GROOVY-4189,我试图用它来规避这个问题,但没有解释它。不过,我相信它确实与 GROOVY-6847 密切相关,因此感谢您指出。我赞成你的回答,但我一直没有解决这个问题,因为这种行为仍然无法解释。再次感谢。
    • 如果没有错误符合您的情况,请考虑打开 JIRA 票
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 2013-08-22
    • 1970-01-01
    • 1970-01-01
    • 2013-01-02
    • 2014-03-17
    • 2012-07-26
    相关资源
    最近更新 更多