Groovy 对象和 MOP

Groovy 对象

Groovy 中的对象其实本质也是 Java 对象,只不过比 Java 对象附加了一些其它的功能。在 Groovy 中的对象,其顶级父类也是 java.lang.Object,同时其也实现了 groovy.lang.GroovyObject 接口。

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);
}

我们的 Groovy 中的对象是可以直接调用 GroovyObject 接口中的方法,然而我们定义的 Groovy 类中没有自己实现过该接口中的任何方法,因此其必定有一个类帮我们实现好了这些方法。现在还未得到证实,但是我猜测是 GroovyObjectSupport 这个类提供的实现,并且在运行时注入到了我们的 Groovy 类中,所以我们才能调用这些方法。

public abstract class GroovyObjectSupport implements GroovyObject {

    // never persist the MetaClass
    private transient MetaClass metaClass;

    public GroovyObjectSupport() {
        this.metaClass = InvokerHelper.getMetaClass(this.getClass());
    }

    public Object getProperty(String property) {
        return getMetaClass().getProperty(this, property);
    }

    public void setProperty(String property, Object newValue) {
        getMetaClass().setProperty(this, property, newValue);
    }

    public Object invokeMethod(String name, Object args) {
        return getMetaClass().invokeMethod(this, name, args);
    }

    public MetaClass getMetaClass() {
        if (metaClass == null) {
            metaClass = InvokerHelper.getMetaClass(getClass());
        }
        return metaClass;
    }

    public void setMetaClass(MetaClass metaClass) {
        this.metaClass = metaClass;
    }
}

这里重点关注一个对象 MetaClass。在 Groovy 的世界中,每个对象(不管是 Groovy 对象还是 Java 对象)都包含一个 MetaClass 对象,该 MetaClass 对象持有其所依附的对象的所有信息(包括属性和方法),每当我们调用一个对象的方法时,都是由该 MetaClass 对象负责路由对方法的调用。我们知道一旦一个类被加载进 JVM,那么这个类就无法修改了,但是我们可以修改这个类的 MetaClass 对象,从而实现对类动态的添加方法和行为。

Groovy 中还有一种特殊的对象——实现了 GroovyInterceptable 接口的类,GroovyInterceptable 接口是一个标记接口,其扩展了 GroovyObject,对于实现了该接口的对象而言,只要调用该对象上的任何方法,都会被 invokeMethod 方法拦截。(要实现拦截,不仅要在类的定义中声明实现该接口,同时还要重写其 invokeMethod 方法,才会有拦截的效果)

public interface GroovyInterceptable extends GroovyObject {
}

Groovy 中的方法调用机制

image.png

该图描述了 Groovy 中方法调用的路由机制。这里做以下补充:

  1. invokeMethod 方法是 GroovyObject 接口中的方法,所有的 Groovy 类都默认实现了该方法。而 GroovyInterceptable 只是一个标记接口,该接口的作用是将 invokeMethod 方法的调用时机提前到了最前面,也就是所有的方法调用都会先统一路由到 invokeMethod 方法中,若为实现 GroovyInterceptable 接口,那么 invokeMethod 方法只有最后才有机会执行。
  2. 若在类的定义中声明了 GroovyInterceptable 接口,但是在类中没有覆盖 invokeMethod 方法,则等同于没有实现 GroovyInterceptable 接口,路由转向左侧。
  3. 若未实现 GroovyInterceptable 接口,而一个类的外部直接调用了 invokeMethod 方法,那么就是方法的直接调用了,不存在拦不拦截的问题,但是如果该类中又没有覆盖 invokeMethod 方法,那么会调用 methodMissing 方法(如果有的话)
  4. 若向一个类的 metaClass 中添加了 invokeMethod 方法或者 methodMissing 方法,在外部调用一个不存在的方法时,会路由到该 invokeMethod 方法上,如果没有实现 invokeMethod 方法,那么会路由到 metaClass 上的 methodMissing 方法上(如果有的话)

提供一个例子直观的感受下 groovy 的方法路由机制

class TestMethodInvocation extends GroovyTestCase {
    void testInterceptedMethodCallonPOJO() {
        def val = new Integer(3)
        Integer.metaClass.toString = { -> 'intercepted' }

        assertEquals "intercepted", val.toString()
    }

    //实现了 GroovyInterceptable 接口,复写 invokeMethod,那么所有的方法调用,都会被路由到 invokeMethod 中
    void testInterceptableCalled() {
        def obj = new AnInterceptable()
        assertEquals 'intercepted', obj.existingMethod()
        assertEquals 'intercepted', obj.nonExistingMethod()
        assertEquals 'intercepted', obj.invokeMethod("existingMethod", null)
        assertEquals 'intercepted', obj.invokeMethod("nonExistingMethod", null)
    }

    void testInterceptedExistingMethodCalled() {
        //将原有的 ex2 方法覆盖了
        AGroovyObject.metaClass.existingMethod2 = { -> 'intercepted' }
        def obj = new AGroovyObject()
        assertEquals 'intercepted', obj.existingMethod2()
    }

    void testUnInterceptedExistingMethodCalled() {
        def obj = new AGroovyObject()
        assertEquals 'existingMethod', obj.existingMethod()
    }

    void testPropertyThatIsClosureCalled() {
        def obj = new AGroovyObject()
        assertEquals 'closure called', obj.closureProp()
    }

    void testMethodMissingCalledOnlyForNonExistent() {
        def obj = new ClassWithInvokeAndMissingMethod()
        assertEquals 'existingMethod', obj.existingMethod()
        assertEquals 'missing called', obj.nonExistingMethod()
        assertEquals 'invoke called', obj.invokeMethod("haha", null)

    }

    void testInvokeMethodCalledForOnlyNonExistent() {
        def obj = new ClassWithInvokeOnly()
        assertEquals 'existingMethod', obj.existingMethod()
        assertEquals 'invoke called', obj.nonExistingMethod()
    }

    void testClassWithMethodMissingOnly(){
        def obj = new ClassWithMethodMissingOnly()
        assertEquals 'existingMethod', obj.existingMethod()
        assertEquals 'missing called', obj.nonExistingMethod()
        assertEquals 'missing called', obj.invokeMethod("haha",null)
    }
    void testMethodFailsOnNonExistent() {
        def obj = new TestMethodInvocation()
        shouldFail(MissingMethodException) { obj.nonExistingMethod() }
    }
}

// 实现了 GroovyInterceptable 接口,复写 invokeMethod,那么所有的方法调用,都会被路由到 invokeMethod 中
class AnInterceptable implements GroovyInterceptable {
    def existingMethod() {}

    def invokeMethod(String name, args) { 'intercepted' }
}

// 普通的 Groovy 类
class AGroovyObject {
    def existingMethod() { 'existingMethod' }

    def existingMethod2() { 'existingMethod2' }
    def closureProp = { 'closure called' }
}

// 普通的 Groovy 类,并实现 invokeMethod 和 methodMissing 方法
class ClassWithInvokeAndMissingMethod {
    def existingMethod() { 'existingMethod' }

    def invokeMethod(String name, args) { 'invoke called' }

    def methodMissing(String name, args) { 'missing called' }
}

// 普通的 Groovy 类,并实现 invokeMethod
class ClassWithInvokeOnly {
    def existingMethod() { 'existingMethod' }

    def invokeMethod(String name, args) { 'invoke called' }
}

class ClassWithMethodMissingOnly {
    def existingMethod() { 'existingMethod' }

    def methodMissing(String name, args) { 'missing called' }
}

MOP(MetaObject Protocol)

MOP:元对象协议。由 Groovy 语言中的一种协议。该协议的出现为元编程提供了优雅的解决方案。而 MOP 机制的核心就是 MetaClass。
元编程:编写能够操作程序的程序,也包括操作程序自身。

正是 Groovy 提供了 MOP 的机制,才使得 Groovy 对象更加灵活,我们可以根据对象的 metaClass,动态的查询对象的方法和属性。这里所属的动态指的是在运行时,根据所提供的方法或者属性的字符串,即可得到
有点类似于 Java 中的反射,但是在使用上却比 Java 中的反射简单的多。

动态访问方法

常用的方法有:

  • getMetaMethod()
  • resondsTo()
  • hasProperty()
  • ......

在使用了 getMetaMethod 方法得到一个 MetaMethod 后,可以调用其 invoke 方法执行。

str = 'hello'
targetMethod = str.metaClass.getMetaMethod('toUpperCase')
targetMethod.invoke(str)

动态访问方法或属性——Groovy 的语法糖

class Foo{
    int bar
    def func1(){}
}

foo = new Foo()

String propertyName = 'bar'
String methodName = 'func1'

//访问属性
foo[propertyName]
foo."$propertyName"

//访问方法
foo."$methodName"()
foo.invokeMethod(methodName,null)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,355评论 6 343
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Groovy学习目录-传送门 元编程(Metaprogramming)->百度百科 Groovy语言支持两种类型的...
    化作春泥_阅读 8,960评论 0 19
  • groovy是什么 Groovy 是下一代的Java语言,跟java一样,它也运行在 JVM 中。 作为跑在JVM...
    ronaldo18阅读 655评论 0 4
  • 一句话总结:小狗钱钱是一本值得一读的好书,一本我愿意推荐给我的孩子读的入门理财书。越早读,越好。 以下是几个值得记...
    AriseLu阅读 220评论 0 0