运行时和编译时元编程—运行时元编程(一)

运行时和编译时元编程 第一部分

Groovy语言支持两种风格的元编程:运行时元编程和编译时元编程。第一种元编程支持在程序运行时修改类模型和程序行为,而第二种发生在编译时。两种元编程有各自的优缺点,在这一章节我们将详细讨论。

注:译者也是第一次接触Groovy,由于时间和水平有限(姑且让译者使用这个理由吧,对待知识本应该一丝不苟)部分专有名词可能翻译不准确甚至有误(读者阅读的过程中最好能参考原文),恳请读者不吝留言指出,谢谢!

1.运行时元编程

通过运行时元编程,我们可以推迟运行时的分支决策(译者注:该处原文为we can postpone to runtime the decision,对于decision,译者也找不到一个合适的表达,请读者根据下图和上下文理解,如果读者有更好的翻译请留言指出,谢谢)来拦截,注入甚至合成类或接口的方法。对于Groovy MOP(译者注:对于初学者,这里突然冒出这个新名词,译者也头大,通过查询,MOP是Mete Object Protocol的缩写,读者可参考该文来了解)的更深理解,我们需要理解Groovy的对象和方法处理。在Groovy里,我们主要使用三种类型的对象:POJO,POGO和Groovy拦截器。Groovy支持元编程多种方式来对这些类型对象进行元编程。

  • POJO – 一个普通的Java对象,它的类可以使用Java或其他支持JVM的语言来编写。
  • POGO – 一个Groovy对象,类用Groovy实现。默认继承了java.lang.Object并且实现了groovy.lang.GroovyObject接口。
  • Groovy 拦截器 – 实现了groovy.lang.GroovyInterceptable接口并且具有方法拦截能力的Groovy对象,我们将在GroovyInterceptable这一节详细讨论。

对于每次方法调用,Groovy都会检查对象是一个POJO还是一个POGO。对于POJOs,Groovy从groovy.lang.MetaClassRegistry类中携带元信息并且委托方法来调用。对于POGOs,Groovy有更复杂的不知,我们在下图演示:

1.1 GroovyObject接口

Groovy.lang.GroovyObject的地位和Java中的Object类一样,是一个主接口。GroovyObject有一个默认的实现类groovy.lang.GroovyObjectSupport,这个类的主要职责是转换groovy.lang.MetaClass对象的调用。GroovyObject源码类似下面这样

01 package groovy.lang;
02  
03 public interface GroovyObject {
04  
05     Object invokeMethod(String name, Object args);
06  
07     Object getProperty(String propertyName);
08  
09     void setProperty(String propertyName, Object newValue);
10  
11     MetaClass getMetaClass();
12  
13     void setMetaClass(MetaClass metaClass);
14 }

1.1.1 invokeMethod

根据运行时元编程的规定,当你调用的方法不是Groovy对象时将会调用这个方法。这儿有一个简单的示例演示重载invokeMethod()方法:

01 class SomeGroovyClass {
02  
03     def invokeMethod(String name, Object args) {
04         return "called invokeMethod $name $args"
05     }
06  
07     def test() {
08         return 'method exists'
09     }
10 }
11  
12 def someGroovyClass = new SomeGroovyClass()
13  
14 assert someGroovyClass.test() == 'method exists'
15 assert someGroovyClass.someMethod() == 'called invokeMethod someMethod []'

1.1.2 get/setProperty

通过重载当前对象的getProperty()方法可以使每次读取属性时被拦截。下面是一个简单的示例:

01 class SomeGroovyClass {
02  
03     def property1 = 'ha'
04     def field2 = 'ho'
05     def field4 = 'hu'
06  
07     def getField1() {
08         return 'getHa'
09     }
10  
11     def getProperty(String name) {
12         if (name != 'field3')
13             return metaClass.getProperty(this, name)                  //(1)
14         else
15             return 'field3'
16     }
17 }
18 def someGroovyClass = new SomeGroovyClass()
19  
20 assert someGroovyClass.field1 == 'getHa'
21 assert someGroovyClass.field2 == 'ho'
22 assert someGroovyClass.field3 == 'field3'
23 assert someGroovyClass.field4 == 'hu'

(1) 将请求的getter转到除field3之外的所有属性
你可以重载setProperty()方法来拦截写属性:

01 class POGO {
02  
03     String property
04  
05     void setProperty(String name, Object value) {
06         this.@"$name" = 'overriden'
07     }
08 }
09  
10 def pogo = new POGO()
11 pogo.property = 'a'
12  
13 assert pogo.property == 'overriden'

1.1.3 get/setMetaClass

你可以访问一个对象的metaClass或者通过改变默认的拦截机制来设置实现你自己的MetaClass。比如说你通过写你自己的MetaClass实现接口来将一套拦截机制分配到一个对象上:

1 // getMetaclass
2 someObject.metaClass
3  
4 // setMetaClass
5 someObject.metaClass = new OwnMetaClassImplementation()

你可以在GroovyInterceptable专题里找到更多的例子。

1.2 get/setAttribute

这个功能和MetaClass实现类相关。在该类默认的实现里,你可以无需调用他们的getter和setters方法来访问属性。下面是一个示例:

01 class SomeGroovyClass {
02  
03     def field1 = 'ha'
04     def field2 = 'ho'
05  
06     def getField1() {
07         return 'getHa'
08     }
09 }
10  
11 def someGroovyClass = new SomeGroovyClass()
12  
13 assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field1') == 'ha'
14 assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field2') == 'ho'
01 class POGO {
02  
03     private String field
04     String property1
05  
06     void setProperty1(String property1) {
07         this.property1 = "setProperty1"
08     }
09 }
10  
11 def pogo = new POGO()
12 pogo.metaClass.setAttribute(pogo, 'field', 'ha')
13 pogo.metaClass.setAttribute(pogo, 'property1', 'ho')
14  
15 assert pogo.field == 'ha'
16 assert pogo.property1 == 'ho'

1.3 MethodMissing

Groovy支持methodMissing的概念。这个方法不同于invokeMethod,它只能在方法分发失败的情况下调用,当给定的名字或给定的参数无法找到时被调用:

1 class Foo {
2  
3    def methodMissing(String name, def args) {
4         return "this is me"
5    }
6 }
7  
8 assert new Foo().someUnknownMethod(42l) == 'this is me'

当我们使用methodMissing的时候,如果下一次同样一个方法被调用其返回的结果可能是缓存的。比如说,考虑在GORM的动态查找器,有一个methodMissing的实现,下面是具体的代码:

01 class GORM {
02  
03    def dynamicMethods = [...] // an array of dynamic methods that use regex
04  
05    def methodMissing(String name, args) {
06        def method = dynamicMethods.find { it.match(name) }
07        if(method) {
08           GORM.metaClass."$name" = { Object[] varArgs ->
09              method.invoke(delegate, name, varArgs)
10           }
11           return method.invoke(delegate,name, args)
12        }
13        else throw new MissingMethodException(name, delegate, args)
14    }
15 }

注意,如果我们发现一个方法要被调用,我们会使用ExpandoMetaClass动态注册一个新的方法在上面。这就是为什么下次相同的方法被调用将会更加快。使用methodMissing并没有invokeMethod的开销大。而且如果是第二次调用将基本没有开销。

1.4 propertyMissing

Groovy支持propertyMissing的概念,用于拦截可能存在的属性获取失败。在getter方法里,propertyMissing使用单个String类型的参数来代表属性名字:

1 class Foo {
2    def propertyMissing(String name) { name }
3 }
4  
5 assert new Foo().boo == 'boo'

在Groovy运行时,propertyMissing(String)方法只有在没有任何getter方法可以被给定的property所找到才会被调用。
对于setter方法,可以添加第二个propertyMissing定义来添加一个额外的值参数

01 class Foo {
02    def storage = [:]
03    def propertyMissing(String name, value) { storage[name] = value }
04    def propertyMissing(String name) { storage[name] }
05 }
06  
07 def f = new Foo()
08 f.foo = "bar"
09  
10 assert f.foo == "bar"

methodMissing方法的最适用地方在动态注册新的属性时能极大提供查找属性所花费的性能。
methodMissing和propertyMissing方法可以通过ExpandoMetaClass来添加静态方法和属性。

1.5 GroovyInterceptable

Groovy.lang.GroovyInterceptable接口是一个继承了GroovyObject的标记接口,在Groovy运行时,用于标记所有方法可以通过Groovy的方法分发机制被拦截。

1 package groovy.lang;
2  
3 public interface GroovyInterceptable extends GroovyObject {
4 }

当一个Groovy对象实现了GroovyInterceptable接口,它的invokeMethod()将在任何方法调用时被调用。下面是这个类型的一个简单示例:

1 class Interception implements GroovyInterceptable {
2  
3     def definedMethod() { }
4  
5     def invokeMethod(String name, Object args) {
6         'invokedMethod'
7     }
8 }

下一块代码是一个测试类,不管调用存在的方法还是不存在的方法都将返回相同的结果。

1 class InterceptableTest extends GroovyTestCase {
2  
3     void testCheckInterception() {
4         def interception = new Interception()
5  
6         assert interception.definedMethod() == 'invokedMethod'
7         assert interception.someMethod() == 'invokedMethod'
8     }
9 }

我们不能使用默认的Groovy方法比如println,因为这些方法是被注入到Groovy对象中区,因此它们也会被拦截。
如果我们想拦截所有所有方法但又不想实现GroovyInterceptable接口,我们可以在一个对象的MetaClass类上实现invokeMethod()。对于POGOs和POJOs,这种方式都是可以的。下面是一个示例:

01 class InterceptionThroughMetaClassTest extends GroovyTestCase {
02  
03     void testPOJOMetaClassInterception() {
04         String invoking = 'ha'
05         invoking.metaClass.invokeMethod = { String name, Object args ->
06             'invoked'
07         }
08  
09         assert invoking.length() == 'invoked'
10         assert invoking.someMethod() == 'invoked'
11     }
12  
13     void testPOGOMetaClassInterception() {
14         Entity entity = new Entity('Hello')
15         entity.metaClass.invokeMethod = { String name, Object args ->
16             'invoked'
17         }
18  
19         assert entity.build(new Object()) == 'invoked'
20         assert entity.someMethod() == 'invoked'
21     }
22 }

关于MetaClass类的详细信息可以在MetaClass章节找到。

1.6 Categories

有这样一种场景,如果能让一个类的某些方法不受控制将会是很有用的。为了实现这种可能性,Groovy从Object-C借用实现了一个特性,叫做Categories。
Categories特性实现了所谓的category类,一个category类是需要满足某些特定的预定义的规则来定义一些拓展方法。
下面有几个categories是在Groovy环境中系统提供的一些额外功能:

Category类默认是不能使用的,要使用这些定义在一个category类的方法需要使用 use 方法,这个方法是GDK提供的一个内置于Groovy对象中的实例:

1 use(TimeCategory)  {
2     println 1.minute.from.now           //(1)
3     println 10.hours.ago
4  
5     def someDate = new Date()          //(2)
6     println someDate - 3.months
7 }

(1) TimeCategory添加一个方法到Integer
(2) TimeCategory添加一个方法到Date
use 方法把category类作为第一个参数,一个闭包代码块作为第二个参数。在Closure里可以访问catetory。从上面的例子可以看到,即便是JDK的类,比如java.lang.Integer或java.util.Date也是可以被包含到用户定义的方法里的。
一个category不需要直接暴露给用户代码,下面的示例说明了这一点:

01 class JPACategory{
02   // Let's enhance JPA EntityManager without getting into the JSR committee
03   static void persistAll(EntityManager em , Object[] entities) { //add an interface to save all
04     entities?.each { em.persist(it) }
05   }
06 }
07  
08 def transactionContext = {
09   EntityManager em, Closure c ->
10   def tx = em.transaction
11   try {
12     tx.begin()
13     use(JPACategory) {
14       c()
15     }
16     tx.commit()
17   } catch (e) {
18     tx.rollback()
19   } finally {
20     //cleanup your resource here
21   }
22 }
23  
24 // user code, they always forget to close resource in exception, some even forget to commit, let's not rely on them.
25 EntityManager em; //probably injected
26 transactionContext (em) {
27  em.persistAll(obj1, obj2, obj3)
28  // let's do some logics here to make the example sensible
29  em.persistAll(obj2, obj4, obj6)
30 }

如果我们去看groovy.time.TimeCategory类的嗲吗我们会发现拓展方法都是被声明为static方法。事实上,一个category类的方法要能被成功地加到use代码块里必须要这样写:

01 public class TimeCategory {
02  
03     public static Date plus(final Date date, final BaseDuration duration) {
04         return duration.plus(date);
05     }
06  
07     public static Date minus(final Date date, final BaseDuration duration) {
08         final Calendar cal = Calendar.getInstance();
09  
10         cal.setTime(date);
11         cal.add(Calendar.YEAR, -duration.getYears());
12         cal.add(Calendar.MONTH, -duration.getMonths());
13         cal.add(Calendar.DAY_OF_YEAR, -duration.getDays());
14         cal.add(Calendar.HOUR_OF_DAY, -duration.getHours());
15         cal.add(Calendar.MINUTE, -duration.getMinutes());
16         cal.add(Calendar.SECOND, -duration.getSeconds());
17         cal.add(Calendar.MILLISECOND, -duration.getMillis());
18  
19         return cal.getTime();
20     }
21  
22     // ...

另外一个要求是静态方法的第一个参数必须定义类型,只要方法被激活。另外一个参数可以作为一个普通的参数当成方法的变量。
因为参数和静态方法的转变,category方法的定义可能比一般的方法定义不那么直观。不过Groovy提供了一个@Category注解,可以在编译时将一个类转化为category类。

01 class Distance {
02     def number
03     String toString() { "${number}m" }
04 }
05  
06 @Category(Number)
07 class NumberCategory {
08     Distance getMeters() {
09         new Distance(number: this)
10     }
11 }
12  
13 use (NumberCategory)  {
14     assert 42.meters.toString() == '42m'
15 }

使用@Category注解可以直接使用示例方法二不必将目标类型作为第一个参数的好处。目标类型类在注解里作为了一个参数。
编译时元编程章节里有@Category的详细说明。

1.7 MetaClasses

(TBD)

1.7.1 Custom metaclasses

(TBD)
Delegating metaclass
(TBD)
Magic package(Maksym Stavyskyi)
(TBD)

1.7.2 Per instance metaclass

(TBD)

时间: 2024-10-02 03:45:37

运行时和编译时元编程—运行时元编程(一)的相关文章

win8 eclipse 编译的程序不能运行

问题描述 win8 eclipse 编译的程序不能运行 win8 eclipse 编译的程序不能运行,总是被系统阻止了 解决方案 用admin权限允许程序.同时程序最好有数字签名 解决方案二: 看下是不是安装了杀毒软件,或者你的程序放在了没有权限的文件夹下.以管理员方式运行呢 解决方案三: 右键eclipse,然后点击"以管理员身份运行",试试看 解决方案四: 应该就是权限的问题, 解决方案五: 那你以管理员身份运行看看,win8对系统安全要求比较高

运行时和编译时元编程—运行时元编程

原文链接   译文链接   译者:JackWang 运行时和编译时元编程 第一部分 Groovy语言支持两种风格的元编程:运行时元编程和编译时元编程.第一种元编程支持在程序运行时修改类模型和程序行为,而第二种发生在编译时.两种元编程有各自的优缺点,在这一章节我们将详细讨论. 注:译者也是第一次接触Groovy,由于时间和水平有限(姑且让译者使用这个理由吧,对待知识本应该一丝不苟)部分专有名词可能翻译不准确甚至有误(读者阅读的过程中最好能参考原文),恳请读者不吝留言指出,谢谢! 1.运行时元编程

运行时和编译时元编程—编译时元编程

原文链接    译文链接     译者:JackWang 运行时和编译时元编程 第二部分 2 编译时元编程 Groovy的编译时元编程支持编译时生成代码.这些变换(译者注:原文该专有名词是transformations,译者直译为变换,也许不准确.如果有知道准确翻译的读者恳请不吝赐教,待译者修正)叫做程序的抽象语法树(AST),在Groovy里,我们叫做AST变换.AST变换支持在编译过程中植入钩子,修改抽象语法树之后继续编译生成正常的字节码流.和运行时元编程相比,这种转换可以在类文件的修改可见

运行时和编译时元编程—运行时元编程(二)

1.7.3 ExpandoMetaclass Groovy有一个特殊的MetaClass类叫做ExpandoMetaClass.它的特别之处在于支持动态添加或修改方法,构造函数,属性,甚至通过使用一个闭包语法来添加或修改静态方法. 这些特性测试场景将会非常使用,具体在测试指南将会说明. 在Groovy里,每一个java.lang.Class类都有一个特殊的metaClass属性,可以通过它拿到一个ExpandoMetaCalss实例.这个实例可以被用于添加方法或修改一个已经存在的方法的行为. 默

为什么程序在vc6.0编译通过并且运行正确但是在编程挑战时提交后却是运行结果不对

问题描述 为什么程序在vc6.0编译通过并且运行正确但是在编程挑战时提交后却是运行结果不对 编程挑战试了几次都是结果不对,但是我在vc6.0编译后运行的结果是正确的啊! 解决方案 这个有很多原因,比如说,你的程序本身有bug,虽然可以通过某些测试用例,但是oj使用的是另外的用例,没有通过,或者是你的程序在运行时间和使用内存上不符合要求,或者你的程序输入输出部分有问题,使得oj程序没法传递正确的测试用例,以及获取你的输出.还可能你使用了额外的库,而oj没有,你的编译器和oj的不同,造成编译上的细微

详解利用 JDK6 动态编译组件搭建 OSGi 运行时编译环境

但是我们知道,在开发 OSGi 环境下的 Bundle 时最麻烦的步骤之一就是搭建编译环境.即便利用 Eclipse 这样高效的 开发工具,由于 Bundle 个数的庞大以及同一 Bundle 的版本多样性,维护一个编译环境变得非常繁琐.常常我们需要对一 个 OSGi 的 Bundle 进行二次开发时,仅仅一个很小的改动都需要花大量的时间去搭建专为这套程序的编译环境.我们迫切 希望可以有一个运行时的编译环境来简化这些步骤,利用环境既有的依赖项来对代码进行编译. 本篇文章介绍 OSGi 的运行特性

iostream-请大神一看!使用tinyxml编译出来的程序运行时的错误!

问题描述 请大神一看!使用tinyxml编译出来的程序运行时的错误! 因为是新手刚学习tinyxml,所以谢了个生成xml文件的cpp试试,编译时没问题,但是程序运行时会出现:xml: malloc.c:2395: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (ol

vc++-计算机图形学的问题,运行时编译成功,但是运行不出,老出以下错误

问题描述 计算机图形学的问题,运行时编译成功,但是运行不出,老出以下错误 Linking... gra.obj : error LNK2005: _main already defined in gra1.obj LIBCD.lib(wincrt0.obj) : error LNK2001: unresolved external symbol _WinMain@16 Debug/graphic1.exe : fatal error LNK1120: 1 unresolved externals

c++-我的代码编译没有问题,但运行时出现访问冲突:

问题描述 我的代码编译没有问题,但运行时出现访问冲突: 0x00421ab3 处未处理的异常: 0xC0000005: 读取位置 0xfdfdfe05 时发生访问冲突 我检查代码发现他是执行到 p= "^(@|$)"; CRegexpT reg( p,IGNORECASE ); MatchResult match_ret = reg.Match( Buf_Line); 发生的,调用堆栈第一行是 Test.exe!CBuilderT::Clear() 行1658 + 0x3e 字节 C+