Runtime and compile-time metaprogramming

文档

http://www.groovy-lang.org/metaprogramming.html


Groovy语言支持两种元编程:运行时Runtime编译时compile-time
Runtime 允许在运行时改变类模型和程序的行为,
compile-time 只发生在编译时。

1、Runtime元编程

通过 Runtime 元编程,我们可以推迟到运行时再决定拦截、注入甚至synthesize(合成)类和接口的方法。

为了深入理解Groovy的元对象协议(MOP),我们需要了解Groovy对象和Groovy的方法处理。

在Groovy中,我们使用三种对象:POJO,POGO和Groovy拦截器。

  • POJO对象表示一个普通的Java对象,其类可以用Java或任何其他语言编写,供JVM使用。

  • POGO对象表示一个Groovy对象,其类是用Groovy编写的。它扩展java.lang.Object并默认实现groovy.lang.GroovyObject接口。

  • Groovy拦截器表示一个Groovy对象,其实现了 groovy.lang.GroovyInterceptable 接口,并且有 method-interception(方法拦截) 的功能。

Groovy允许对所有类型的对象进行元编程,但是不同的类型有不同的方式。

对于每个方法的调用,Groovy都检查对象是 POJO还是POGO 类型。
对于POJO,Groovy从 groovy.lang.MetaClassRegistry 中获取它的 MetaClass,并将方法调用委托给它。

对于POGO,Groovy会采取更多步骤,如下图所示:

Runtime and compile-time metaprogramming_第1张图片
image.png

1.1 GroovyObject 接口

groovy.lang.GroovyObject接口是Groovy中的主要接口,就像Java中的Object
GroovyObject接口在 groovy.lang.GroovyObjectSupport类中有一个默认实现,他负责将调用转移到groovy.lang.MetaClass 对象.

GroovyObject源代码如下所示:

package groovy.lang;

public interface GroovyObject {

    Object invokeMethod(String name, Object args);

    Object getProperty(String propertyName);

    void setProperty(String propertyName, Object newValue);

    MetaClass getMetaClass();

    void setMetaClass(MetaClass metaClass);
}

1.1.1 invokeMethod

此方法主要用于与 GroovyInterceptable接口或对象的MetaClass 结合使用,以拦截 方法调用。

当调用的方法不存在于Groovy对象上时,它也会被调用。
示例:

class SomeGroovyClass {

    def invokeMethod(String name, Object args) {
        return "called invokeMethod $name $args"
    }

    def test() {
        return 'method exists'
    }
}

def someGroovyClass = new SomeGroovyClass()

assert someGroovyClass.test() == 'method exists'
assert someGroovyClass.someMethod() == 'called invokeMethod someMethod []'

但是,不鼓励使用invokeMethod来拦截缺少的方法。
如果意图是只在方法调度失败的情况下 请使用 methodMissing 来拦截方法调用。

1.1.2 get/setProperty

通过覆盖当前对象的getProperty()方法可以拦截对属性的每次读取访问。
您可以通过重写setProperty()方法来拦截对属性的写入访问权限

1.1.3. get/setMetaClass

您可以访问对象的metaClass或设置您自己的MetaClass实现来更改默认拦截机制。
例如,您可以编写自己的MetaClass接口实现,并将其分配给对象以更改拦截机制

1.2 get/setAttribute

这个功能与MetaClass实现有关。
在默认实现中,您可以访问字段而不用调用其getter和setter。
下面的例子演示了这种方法:

class SomeGroovyClass {

    def field1 = 'ha'
    def field2 = 'ho'

    def getField1() {
        return 'getHa'
    }
}

def someGroovyClass = new SomeGroovyClass()

assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field1') == 'ha'
assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field2') == 'ho'
class POGO {

    private String field
    String property1

    void setProperty1(String property1) {
        this.property1 = "setProperty1"
    }
}

def pogo = new POGO()
pogo.metaClass.setAttribute(pogo, 'field', 'ha')
pogo.metaClass.setAttribute(pogo, 'property1', 'ho')

assert pogo.field == 'ha'
assert pogo.property1 == 'ho'

1.3 methodMissing

Groovy支持methodMissing的概念。
此方法不同于invokeMethod之处在于,只有在给定名称和/或给定参数找不到方法的情况下,才会在调用失败的方法时调用它。

class Foo {

   def methodMissing(String name, def args) {
        return "this is me"
   }
}

assert new Foo().someUnknownMethod(42l) == 'this is me'

1.4 propertyMissing

1.5. GroovyInterceptable

groovy.lang.GroovyInterceptable 接口是扩展GroovyObject的标记接口,用于通知Groovy runtime,所有方法都应该通过Groovy runtime的 method dispatcher mechanism 拦截。

当 Groovy对象 实现GroovyInterceptable接口后, 调用它的任何方法都将调用其invokeMethod()方法

简单例子:

class Interception implements GroovyInterceptable {

    def definedMethod() { }

    def invokeMethod(String name, Object args) {
        'invokedMethod'
    }
}
def interception = new Interception()

assert interception.definedMethod() == 'invokedMethod'
assert interception.someMethod() == 'invokedMethod'

如果我们想拦截所有的方法调用,但是不想实现 GroovyInterceptable 接口,我们可以实现object的 MetaClassinvokeMethod() 方法。
这种方式适用于 POJO 和 POGO

1.6. Categories

有些情况下,如果一个不受控制的类别具有其他方法,那么它很有用。
为了实现这种功能,Groovy实现了一个从Objective-C借用的功能,称为Categories(类别)。

不太懂

1.7. Metaclasses

1.7.1. Custom metaclasses

1.7.2. Per instance metaclass

1.7.3. ExpandoMetaClass

Methods
class Book {
   String title
}

Book.metaClass.titleInUpperCase << {-> title.toUpperCase() }

def b = new Book(title:"The Stand")

assert "THE STAND" == b.titleInUpperCase()
Properties
Constructors
class Book {
    String title
}
Book.metaClass.constructor << { String title -> new Book(title:title) }

def book = new Book('Groovy in Action - 2nd Edition')
assert book.title == 'Groovy in Action - 2nd Edition'
Static Methods
class Book {
   String title
}

Book.metaClass.static.create << { String title -> new Book(title:title) }

def b = Book.create("The Stand")
Borrowing Methods
class Person {
    String name
}
class MortgageLender {
   def borrowMoney() {
      "buy house"
   }
}

def lender = new MortgageLender()

Person.metaClass.buyHouse = lender.&borrowMoney

def p = new Person()

assert "buy house" == p.buyHouse()
Dynamic Method Names
Runtime Discovery
GroovyObject Methods
Overriding Static invokeMethod

1.8. Extension modules

1.8.1. Extending existing classes

例子

def file = new File(...)
def contents = file.getText('utf-8')

File类本来没有 getText 方法, 然而,Groovy 知道它,因为这个方法定义在了一个特殊的类中:ResourceGroovyMethods

public static String getText(File file, String charset) throws IOException {
 return IOGroovyMethods.getText(newReader(file, charset));
}

你可能已经注意到扩展的方法被定义为一个静态方法,该方法的第一个参数是相应的receiver,其余的参数是扩展方法需要的参数。

创建一个扩展模块非常简单:
(1) 像上面一样写一个扩展类
(2) 写一个模块描述文件

然后你需要让扩展模块对Groovy可见,这只需要简单的将扩展类和描述文件放到 classpath 中即可。
这意味着你可以有两个选择:
(1) 直接在 classpath 上 提供 classes 和 模块描述文件
(2) 或者 将他们打包到一个jar文件 以便复用

扩展模块可以增加两种类型的方法到类中: 实例方法和静态方法

1.8.2. Instance methods

class MaxRetriesExtension { 
    
    static void maxRetries(Integer self, Closure code) {        
        assert self >= 0
        int retries = self
        Throwable e = null
        while (retries > 0) {
            try {
                code.call()
                break
            } catch (Throwable err) {
                e = err
                retries--
            }
        }
        if (retries == 0 && e) {
            throw e
        }
    }
}

将其 声明为你的扩展类 之后,你就可以使用下面的方式调用它:

int i=0
5.maxRetries {
    i++
}
assert i == 1
i=0
try {
    5.maxRetries {
        i++
        throw new RuntimeException("oops")
    }
} catch (RuntimeException e) {
    assert i == 5
}

1.8.3. Static methods

要为类增加静态方法, 需要将静态方法声明在其自己的文件中。
静态方法和实例方法不能在同一个文件中

1.8.4. Module descriptor

为了让Groovy可以加载你的扩展方法,你必须声明你的 extension helper classes
你必须创建一个文件,名称为 org.codehaus.groovy.runtime.ExtensionModuleMETA-INF/services 文件夹内

org.codehaus.groovy.runtime.ExtensionModule 内容

moduleName=Test module for specifications
moduleVersion=1.0-test
extensionClasses=support.MaxRetriesExtension
staticExtensionClasses=support.StaticStringExtension

2、Compile-time 元编程

....

你可能感兴趣的:(Runtime and compile-time metaprogramming)