掌握Gradle,还需要掌握这些知识--Groovy MOP

Posted leobert_lan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了掌握Gradle,还需要掌握这些知识--Groovy MOP相关的知识,希望对你有一定的参考价值。

Groovy:MOP一文打尽

写在最前

Groovy已经不再是一门新出现的语言,而笔者是在2013年左右接触到它的,并且在2017年时,有机会尝试使用它编写了基于SpringBoot的后端项目。

但说来惭愧,在很长的一段时间里,我都没有系统的学习它。并且时至今日,我也 不推荐 大家再去 系统的学习 它,毕竟 使用它的机会越发地少了
但是我依旧认为大家有必要花费一些零碎的时间,快餐式的了解它。

这一篇讲MOP,之后还有一篇闭包

文章代码已发布于:GroovyWorkshop

为何产生编写Groovy系列的想法

一言以蔽之:“被刺激到了,很多事情不知其所以然”.

完整了解

MetaObject Protocol 元对象协议

本文直接从MOP开始,忽略掉Groovy的大量基础部分,因为这些基础部分,基本和Java一致。

MOP的目标在于:运行期进行实时变化,听起来有点像Java的反射,但是要比Java的反射更加强大。可以:

  • 修改方法名、属性名,
  • 动态增加类的方法、属性 等等

神奇的方法分配-- invokeMethodmethodMissing

首先我们简单了解一下Groovy的方法分配机制:

invokemethod_methodmissing

图片来自网络

本节内容,结果上看起来很神奇,但内容比较枯燥,并且对非语言使用者帮助不大,可以泛读,有个印象即可

invokeMethod 拦截示例

我们先看一个简单的例子:

class Demo1 {

    def foo() {
        println 'foo'
    }

    def invokeMethod(String name, Object args) {
        return "unknown method $name(${args.join(',')})"
    }

    static void main(String[] args) {
        def demo = new Demo1()
        demo.foo()
        println demo.bar("A", "B")
    }

}

如果是Java或者Kotlin,很明显,Demo1 中并没有 bar 方法,编译不能通过,但Groovy可以通过编译,得到运行结果:

> Task :Demo1.main()
foo
unknown method bar(A,B)

这就是所谓的 invokeMethod,Groovy中设计了一个顶层接口:GroovyObject

public interface GroovyObject {
    Object invokeMethod(String var1, Object var2);

    Object getProperty(String var1);

    void setProperty(String var1, Object var2);

    MetaClass getMetaClass();

    void setMetaClass(MetaClass var1);
}

编译结果:

package osp.leobert.groovyworkshop.mop;

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class Demo1 implements GroovyObject {
    @Generated
    public Demo1() {
        CallSite[] var1 = $getCallSiteArray();
        super();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }

    public Object foo() {
        CallSite[] var1 = $getCallSiteArray();
        return var1[0].callCurrent(this, "foo");
    }

    public Object invokeMethod(String name, Object args) {
        CallSite[] var3 = $getCallSiteArray();
        return new GStringImpl(new Object[]{name, var3[1].call(args, ",")}, new String[]{"unknown method ", "(", ")"});
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        Object demo = var1[2].callConstructor(Demo1.class);
        var1[3].call(demo);
        var1[4].callStatic(Demo1.class, var1[5].call(demo, "A", "B"));
    }
}

我们发现,调用 foo() 方法时,并未执行 invokeMethod 中的逻辑,而Groovy中还有一个特殊的接口:GroovyInterceptable,如果实现它的话:

class Demo2 implements GroovyInterceptable {

    def foo(String s) {
        return "foo:$s"
    }

    def invokeMethod(String name, Object args) {
        return "unknown method $name(${args.join(',')})"
    }

    static void main(String[] args) {
        def demo = new Demo2()
        println demo.foo("a")
        println demo.bar("A", "B")
    }
}

我们将得到如下结果:

> Task :Demo2.main()
unknown method foo(a)
unknown method bar(A,B)

foo() 方法并未被分配!!, 这个场景很容易让我们联想到 Java的动态代理拦截、自行分配方法执行

methodMissing示例

class Demo3 {

    def foo(String s) {
        return "foo:$s"
    }

    def invokeMethod(String name, Object args) {
        return "unknown method $name(${args.join(',')})"
    }

    def methodMissing(String name, Object args) {
        return "methodMissing $name(${args.join(',')})"
    }

    static void main(String[] args) {
        def demo = new Demo3()
        println demo.foo("a")
        println demo.bar("A", "B")
    }
}

按照前文的分派流程图,我们猜到结果为:

> Task :Demo3.main()
foo:a
methodMissing bar(A,B)

而实现了 GroovyInterceptable 的情况下,就无法再利用methodMissing机制拦截,而是按照 GroovyInterceptable 走 invokeMethod拦截

class Demo4 implements GroovyInterceptable {

    def foo(String s) {
        return "foo:$s"
    }

    def invokeMethod(String name, Object args) {
        return "unknown method $name(${args.join(',')})"
    }

    def methodMissing(String name, Object args) {
        return "methodMissing $name(${args.join(',')})"
    }

    static void main(String[] args) {
        def demo = new Demo4()
        println demo.foo("a")
        println demo.bar("A", "B")
    }
}
> Task :Demo4.main()
unknown method foo(a)
unknown method bar(A,B)

动态处理类的属性

一个有关的题外话

一个传统的 简单JavaBean,在很多场景下又称为 POJO ,大家对此不会陌生,它包含了属性和属性的Getter、Setter并且不包含任意逻辑。

我们知道, POJO需要添加Getter、Setter,哪怕通过IDE生成,并且编译时如果可能,会被inline优化,为此,还有 是否该使用Lombok之争

但是,对于 “应当有Getter、Setter,但是不应当由编写者处理,而是应该由编译器处理” 是多数人认同的

Groovy中对此进行了尝试,提供了 GPath机制:通过编译器直接生成Getter、Setter,编码时形如属性访问,用"."符 foo.bar,实际却相对复杂。

kotlin中也有类似的机制。

class GpathDemo {

    static class Foo {
        String bar

        def getBaz() {
            return "baz"
        }
    }

    static void main(String[] args) {
        Foo foo = new Foo(bar:"bar")
        foo.bar = "bar 2"
        println(foo.bar)
        println(foo.baz)
    }
}

我们可以发现,生成的类:

 public static class Foo implements GroovyObject {
    private String bar;

    @Generated
    public Foo() {
        CallSite[] var1 = $getCallSiteArray();
        super();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }

    public Object getBaz() {
        CallSite[] var1 = $getCallSiteArray();
        return "baz";
    }

    @Generated
    public String getBar() {
        return this.bar;
    }

    @Generated
    public void setBar(String var1) {
        this.bar = var1;
    }
}
public static void main(String... args) {
    CallSite[] var1 = $getCallSiteArray();
    GpathDemo.Foo foo = (GpathDemo.Foo)ScriptBytecodeAdapter
            .castToType(var1[0].callConstructor(GpathDemo.Foo.class,
                    ScriptBytecodeAdapter.createMap(new Object[]{"bar", "bar"})),
                    GpathDemo.Foo.class);
    String var3 = "bar 2";
    ScriptBytecodeAdapter.setProperty(var3, (Class)null, foo, (String)"bar");
    var1[1].callStatic(GpathDemo.class, var1[2].callGetProperty(foo));
    var1[3].callStatic(GpathDemo.class, var1[4].callGetProperty(foo));
}

读者可能已经注意到了,通过手动添加Getter,也可以利用GPath机制,用"."访问;

另外,读者可能也注意到:设置bar属性时,并未直接访问Setter,此处,我们可以动态的添加属性!

class GpathDemo {

    static class Bar {
    }

    static void main(String[] args) {
        Bar.metaClass."getBaz" = { ->
            return "baz"
        }

        Bar bar = new Bar()
        println(bar.baz)
    }
}

从编译结果看:

public static class Bar implements GroovyObject {
    @Generated
    public Bar() {
        CallSite[] var1 = $getCallSiteArray();
        super();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }
}

// main:
public static void main(String... args) {
    CallSite[] var1 = $getCallSiteArray();
    final class _main_closure1 extends Closure implements GeneratedClosure {
        public _main_closure1(Object _outerInstance, Object _thisObject) {
            CallSite[] var3 = $getCallSiteArray();
            super(_outerInstance, _thisObject);
        }

        public Object doCall() {
            CallSite[] var1 = $getCallSiteArray();
            return "baz";
        }
    }

    _main_closure1 var4 = new _main_closure1(GpathDemo.class, GpathDemo.class);
    ScriptBytecodeAdapter.setProperty(var4, (Class)null, 
            var1[5].callGetProperty(GpathDemo.Bar.class), (String)"getBaz");
    
    GpathDemo.Bar bar = (GpathDemo.Bar)ScriptBytecodeAdapter.castToType(
            var1[6].callConstructor(GpathDemo.Bar.class),
            GpathDemo.Bar.class);
    var1[7].callStatic(GpathDemo.class, var1[8].callGetProperty(bar));
}

此时,在运行期增加了属性!

如果对Kotlin的扩展和代理比较熟悉,此处应该不难理解

但Groovy的设计更加有趣:

追踪:

  • org.codehaus.groovy.runtime.InvokerHelper#getProperty
  • org.codehaus.groovy.runtime.InvokerHelper#setProperty

发现会进入:GroovyObject,前面已经接触过

public interface GroovyObject {
    Object invokeMethod(String var1, Object var2);

    Object getProperty(String var1);

    void setProperty(String var1, Object var2);

    MetaClass getMetaClass();

    void setMetaClass(MetaClass var1);
}

那么借助集合,如 Map ,并复写 getPropertysetProperty,就可以做一些有趣的事情

特殊的Expando类

哈哈,这个有趣的事情Groovy已经做了,这就是 Expando 类。

class ExpandoDemo {

    static void main(String[] args) {
        Expando expando = new Expando()
        expando.foo = "foo"
        println(expando.foo)
        expando.bar = "bar"
        println(expando.bar)

        expando.properties.forEach(new BiConsumer() {
            @Override
            void accept(Object o, Object o2) {
                println("key:$o,value:$o2")
            }
        })
    }
}
> Task :ExpandoDemo.main()
foo
bar
key:bar,value:bar
key:foo,value:foo

利用ExpandoMetaClass实现Mixin机制

Mixin 即 Mix In,混合, 我们可以笼统地认为:Mixin 即为 在一个类中混入其他类的内容

  • 对于支持多继承的语言,往往是在讨论 多继承 的问题;
  • 对于单继承的语言,Java是利用 接口 制造多继承的表现,基于 组合委托 等方式在目标类中 混入
    规格继承 变相解决问题;Ruby 等语言则引入 Minin实现继承 变相解决问题。

我们不再对此概念进行纠缠,可以认为 “多继承语言可以解决很多问题并带来更多的关联问题,单继承语言想要好处又要规避坏处,部分语言提出了Minin机制”

而Groovy的Minin,除了 编译期 要能混入,还要 运行期混入

看个例子,虽然它的场景很不合理,你一定有一万种理由劝说我使用各类设计模式,但不要较真

class MixinDemo {
    static class Paint {
        def draw(Drawable drawable) {
            println("paint ${drawable.name}")
        }
    }

    static class Drawable {
        String name
    }

    static void main(String[] args) {
        def paint = new Paint()
        Drawable.metaClass.draw = paint.&"draw"
        def drawable = new Drawable(name: "test")
        drawable.draw(drawable)
    }
}

例子中,我们动态的给Drawable添加了draw方法

如果我们将这一过程适当的封装:

class MixinDemo2 {

    static class MixinDelegate {
        private targetClass

        MixinDelegate(targetClass) {
            this.targetClass = targetClass
        }

        def mixin(String asMethodName, Closure closure) {
            targetClass.metaClass."$asMethodName" = closure
        }
    }

    static void main(String[] args) {
        def mixin = new MixinDelegate(MixinDemo.Drawable)
        mixin.mixin("draw",new MixinDemo.Paint().&"draw")
        def drawable = new MixinDemo.Drawable(name: "test")
        drawable.draw(drawable)
    }
}

这将会变得很有趣!!!

假设我们有一套 控制协议 ,在此之前,我们只能在编译期决定好 指令的执行 – 即控制协议实现,即使运用一些巧妙的设计模式,自由程度也很低,
但现在可以在运行时更为自由地扩展、修改

当然,结合前面的知识,我们可以让它更加的酷炫:

class MixinDemo3 {

    static class MixinDsl implements GroovyInterceptable{
        private targetClass

        MixinDsl(targetClass) {
            this.targetClass = targetClass
        }

        def invokeMethod(String s, o) {
            if (s.startsWith("mixinFun") && s.length() > 8 && o[0] instanceof Closure) {
                def methodName = s[8].toLowerCase() + s[9..-1]
                targetClass.metaClass."$methodName" = o[0]
                return null
            } else {
                println("cannot handle")
            }
        }
    }

    static void main(String[] args) {
        (new MixinDsl(MixinDemo.Drawable)).mixinFunDraw new MixinDemo.Paint().&"draw"

        def drawable = new MixinDemo.Drawable(name: "test")
        drawable.draw(drawable)
    }
}

此时,添加方法的写法呈现出 DSL的风格

运行时的其他修改

前面我们已经学习了在运行时给类添加方法,接下来再了解更多的内容:

添加构造器

这个例子要和Java进行对比

class 以上是关于掌握Gradle,还需要掌握这些知识--Groovy MOP的主要内容,如果未能解决你的问题,请参考以下文章

掌握Gradle,还需要掌握这些知识--Groovy MOP

为什么我们需要掌握Gradle的这些知识?

web前端需要掌握的哪些知识

你需要掌握的babel知识都在这里

你需要掌握的babel知识都在这里

[ 渗透入门篇 ] 从渗透测试执行标准着手的渗透学习大纲。掌握了这些知识点还担心找不到工作?