Перезаписать свойство метакласса в Groovy - PullRequest
0 голосов
/ 07 июня 2018

Я хотел бы перезаписать динамическое свойство в метаклассе, но оно не работает так, как я ожидал.Вот простой пример, который демонстрирует проблему.Вы можете запустить его в режиме онлайн на Groovy веб-консоли

class Bike {}
class Bell { def ring() { println("calling " + this) } }

def createNamespaces = {
    return [ "bell": new Bell() ]
}

def resetNamespaces = { bike ->
    // setting to null means 'set it to the default'
    bike.metaClass = null

    createNamespaces().each { name, namespace ->
        println("setting " + namespace)
        bike.metaClass."$name" = namespace
    }
}

def bike= new Bike()

resetNamespaces(bike)
bike.bell.ring()
resetNamespaces(bike)
bike.bell.ring()

Результат:

setting Bell@14e9bd2b
calling Bell@14e9bd2b
setting Bell@948a7ad
calling Bell@14e9bd2b

Поэтому, хотя я изменил свойство в метаклассе, вызывая его всегдавозвращает первый объект, который был установлен.Есть ли какое-то кеширование?

Когда я просто изменяю последнюю часть примера на:

resetNamespaces(bike)
resetNamespaces(bike)
bike.bell.ring()

Тогда результат будет таким, как ожидалось.То есть, вызов свойства возвращает объект, который был установлен в метаклассе как последний:

setting Bell@5b47e0c7
setting Bell@19f373a4
calling Bell@19f373a4

Я даже попытался установить метакласс вручную следующим образом

def resetNamespaces = { bike ->
    def newMetaClass = new ExpandoMetaClass(Bike.class, true, true)
    newMetaClass.initialize()

    bike.metaClass = newMetaClass

    ...
}

Но результатвсе тот же.Так что должен быть какой-то механизм кеширования.Я не могу найти ничего об этом поведении в документации.

1 Ответ

0 голосов
/ 25 июня 2018

Путаница возникает из-за того, что вы пытаетесь изменить свойство метакласса, а не метод.Как указано в официальной документации Groovy метапрограммирование, свойства доступны с помощью методов метакласса setAttribute / getAttribute.

// throws MissingFieldException!
def resetNamespaces = { bike ->
    createNamespaces().each { name, namespace ->
        bike.metaClass.setAttribute(bike, name, namespace)
    }
}

К сожалению, это работает, только если свойство находится в исходном определении класса, здесь оно выдает MissingFieldException: No such field: bell for class: Bike.

С другой стороны, методы перезаписи работают как шарм и предоставляюттребуемый синтаксис, добавив динамический метод получения:

def resetNamespaces(bike) {
    createNamespaces().each { name, namespace ->
        bike.metaClass."get${name.capitalize()}" = { namespace }
    }
}

def bike = new Bike()
resetNamespaces(bike)
bike.bell.ring()
resetNamespaces(bike)
bike.bell.ring()

На самом деле, расширение также хорошо работает

class Bell {
    def ring() { println this }
}

def bike = new Expando()
bike.createNamespaces = {
    return [ bell : new Bell() ]
}

bike.resetNamespaces = {
    bike.createNamespaces().each { name, namespace ->
        bike."$name" = namespace
    }
}

bike.resetNamespaces bike
bike.bell.ring()
bike.resetNamespaces bike
bike.bell.ring() 
...