文档
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会采取更多步骤,如下图所示:
image.png1.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的 MetaClass
的 invokeMethod()
方法。
这种方式适用于 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.ExtensionModule
到 META-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 元编程
....
网友评论