Groovy简介——运行时MOP

  • 概况

Groovy是一个动态语言,类型不用定义的语言。它运行与JVM之上,编译器会先将Groovy代码编译为Java语言,然后编译为字节码。

 

Groovy在编译的时候会先生成AST(抽象语法树,Abstract Syntax Tree),同时Groovy也提供了AST的操作方法,也就是说在Groovy编译时可以插入一些模板方法甚至根据AST插入对应的方法。

 

还有一个很重要的特性,就是MOP(元对象协议 MetaObject Protocol)和元编程,也就是运行时编程,这让Groovy的灵活性又得到了进一步的提高。

 

  • 语法介绍

Groovy的语法几乎和Java是一致的,只是不需要再静态的确定变量类型,也不需要添加分行符和main方法,编译器会自动识别从上向下识别语句运行。

 

Groovy并不需要再定义一个接口,让实现类来进行实现,只需要在方法中直接调用某个方法,实现类只需要定义这个方法即可。

 

作为一个Java/Android程序员,会很自主的遵守契约精神(严重依赖接口)。我们习惯于将我们的行为定义成接口,这样我们的代码会进行更好的分层和结构设计。但是Groovy或者说很多动态语言都采用了参数的自动类型转换,这也就使得接口设计变得不再那么重要。

 

举个例子:

一个场景,如果我们需要一个人来帮助,由于我们并不知道这个人具体会变成是一个学生还是一个医生更或者是一个其他什么身份的人,作为Java程序员,我们会定义一个Helper接口,并在Helper接口中创建一个helpDoSomething()方法,由实现类去进行实现,然后在需要帮助者的时候将实现类对象传递进来即可。

/**

* 创建一个帮助者接口

*/

public interface Helper{

     void helpDoSomething();

}



public class Man implements Helper{

  public void helpDoSomething(){

  System.out.println("do something");

}

}


/**

* 用到Helper的时候直接传入然后调用方法即可

*/

public void takeHelp(Helper helper){

    helper.helpDoSomething();

}

 

同样的使用Groovy则不需要有接口了

def takeHelp(helper){

    helper.helpDoSomething();

}



class Man{

    void helpDoSomething(){

        println "Man's helping..."

}

}



class Woman{

    void helpDoSomething(){

        println "Woman's helping..."

}

}



takeHelper(new Man())

takeHelper(new Woman())

 

此时Groovy就不需要创建接口了,man和woman只要实现了对应的方法,就可以直接传入使用了。当然如果传入的对象并没有实现对应的方法,Groovy也会做处理,会有一些方法补偿和方法注入的方式,但如果所有的试错方式都失败了,那么运行时就会报错。

所以,使用动态类型语言编程,却没有使用单元测试的自律,就相当于是在玩火!

其他的简单的规则:

 

Integer val = 4

val = “hello”

在Groovy编译器不会报错,但在尝试运行编译成java字节码时会报类型转化错误

 

def val = 4

val = “hello”是没问题的

会自动编译成:

Object val = 4;

String var4 = "hello";

 

闭包与协程

调用一个函数或方法会在程序的执行序列中创建一个新的作用域。我们会在一个入口点(方法最上面的语句)进入函数。在方法完成之后,回到调用者的作用域。

 

协程(Coroutine)则支持多个入口点,每个入口点都是上次挂起的位置。我们可以进入一个函数,执行部分代码,挂起,再回到调用者的上下文或作用域执行的一些代码。之后我们可以在挂起的地方恢复该函数的执行。“与主例程和子例程之间的不对称关系不同,协程之间是完全对称的,可以相互调用的。”

 

协程对于实现某些特殊的逻辑或算法非常方便。例如处理生产者-消费者问题时。生产者会接受一些输入,对输入做一些初始化处理,通知消费者拿走处理过的值做进一步计算,并输出或存储结果。消费着处理它的那部分工作,然后通知生产者以获取更多输入。

 

Coroutine协程例子:


def iterate(n, closure){
    1.upto(n) {
        println "In iterate with value ${it}"
        closure(it)
    }
}

println "Calling iterate"
total = 0
iterate(4){
    total += it
    println "In closure total so far is $total"
}

println 'Done'



输出:

Calling iterate

In iterate with value 1

In closure total so far is 1

In iterate with value 2

In closure total so far is 3

In iterate with value 3

In closure total so far is 6

In iterate with value 4

In closure total so far is 10

Done

 

这就是协程...

 

递归,阶乘:

def factorial

factorial = { int number, BigInteger theFactorial ->
    number == 1? theFactorial: factorial.trampoline(number-1, number*theFactorial)
}.trampoline()

println factorial(5,1)
println factorial(1000,1).bitCount()



结果:

120

3788

 

集合类:

ArrayList


lst = [3,423,23,13,432,2,432,5]



println lst[0]

println lst[-1]



println lst[2..5]

println lst[-3..-6]



sublist = lst[2..5]

println sublist.dump()

sublist[0] = 20 

println lst

结果:

3

5

[23, 13, 432, 2]

[2, 432, 13, 23]

<java.util.ArrayList@18f129 elementData=[23, 13, 432, 2] size=4 modCount=1>

[3, 423, 23, 13, 432, 2, 432, 5]

可以用负数表示从后向前,这不是数组,自动会变成ArrayList

 

 

每个元素变成两倍

lst.collect{ it*2 }用闭包的方式,将元素每个都放到闭包中,并自动返回收集到一个集合中

 

lst.find{ it > 4 } 遍历找到第一个大于4的元素

lst.findAll{ it>4 }遍历找到所有大于4的元素

 

 

  • MOP与元编程

在Java中,使用反射可以在运行时探索程序的结构,以及程序的类、类的方法、方法接受的参数。然而,我们仍然局限于所创建的静态结构。我们无法修改一个对象的类型,或是让它动态的获得行为——至少现在还不能。想象一下,如果可以基于应用的当前状态、或基于应用所接受的输入,动态地添加方法和行为,代码会变得更灵活,我们的创作力和开发效率也会提高。好了,不用想象了——在Groovy中,元编程就提供了这一功能。

 

POJO(Plain Old Java Object) 普通的Java对象

POGO (Plain Old Groovy Object) Groovy创建的对象

 

Groovy拦截器,拓展了GroovyInterceptable的Groovy对象,可以进行方法拦截。

 

对于一个POJO,Groovy会去应用类的MetaClassRegistry取它的MetaClass,并将方法调用委托给它。因此,我们在它的MetaClass上定义的任何拦截器或方法,都优先于POJO原来的方法。

也就是说会先在MetaClass上进行方法的寻找。


class Car implements GroovyInterceptable {
    def check() {
        System.out.println "check called ..."
    }

    def start() {
        System.out.println "start called ..."
    }

    def drive() {
        System.out.println "drive called ..."
    }

    def invokeMethod(String name, args){
        System.out.print "Call to $name intercepted ..."

        if (name != 'check'){
            System.out.print("running filter ...")
            Car.metaClass.getMetaMethod('check').invoke(this, null)
        }

        def validMethod = Car.metaClass.getMetaMethod(name, args)
        if (validMethod != null){
            validMethod.invoke(this, args)
        } else {
            Car.metaClass.invokeMethod(this, name, args)
        }
    }

}


car = new Car()
car.start()
car.drive()
car.check()

try{
    car.speed()
} catch(Exception ex){
    println ex
}



结果:

Call to start intercepted ...running filter ...check called ...

start called ...

Call to drive intercepted ...running filter ...check called ...

drive called ...

Call to check intercepted ...check called ...

Call to speed intercepted ...running filter ...check called ...

groovy.lang.MissingMethodException: No signature of method: Car.speed() is applicable for argument types: () values: []

Possible solutions: sleep(long), sleep(long, groovy.lang.Closure), split(groovy.lang.Closure), check(), start(), grep()

 

 

 

MOP的Groovy实现方式

分类(category)

ExpandoMetaClass

Mixin

 

 

1、分类(category)

 

创建一个StringUtil,添加一个static的方法,用于接受数据。需要调用一个特殊的方法use方法,他接受两个参数,一个分类,一个闭包代码块,需要注入的方法就在这个闭包代码块中。

class StringUtil{

    def static toSSN(self){

        if (self.size() == 9){

            "${self[0..2]}-${self[3..4]}-${self[5..8]}"

        }

    }

}

use(StringUtil){

    println "123456789".toSSN()

    println new StringBuilder("987654321").toSSN()

}

try{

    println "123456789".toSSN()

} catch(MissingMethodException ex){

    println ex.message

}

结果:

123-45-6789

987-65-4321

No signature of method: java.lang.String.toSSN() is applicable for argument types: () values: []

Possible solutions: toSet(), toURI(), toURL(), toURL(), toURI(), toLong()

也可以是这样的

@Category(String)

class StringUtilAnnotated{

    def toSSN(){

        if (size() == 9){

            "${this[0..2]}-${this[3..4]}-${this[5..8]}"

        }

    }

}

use(StringUtilAnnotated){

    println "123456789".toSSN()

    println new StringBuilder("987654321").toSSN()

}

结果:

123-45-6789

Caught: groovy.lang.MissingMethodException: No signature of method: java.lang.StringBuilder.toSSN() is applicable for argument types: () values: []

Possible solutions: toSet(), toURI(), toURL(), toList(), toLong(), toShort()

 

找不到一个StringBuilder.toSSN()的方法,因为我们Category指定了是String类型,其实最后还是会被编译成静态方法与上面一致

 

class FindUtil {

    def static extractOnly(String self, closure) {

        def result = ''

        self.each {

            if (closure(it)) {

                result += it

            }

        }

        result

    }

}

use(FindUtil) {

    println "12345454524123".extractOnly({ it == '4' || it == '5' })

}


结果:

4545454

 

分类提供了一个漂亮的方法注入协议。其效果包含在use()块内的控制流中。一代离开代码块,注入的方法就消失了。当在方法上接受了一个参数时,我们可以对这个参数应用自己的分类。那感觉就像是拓展了所接受对象的类型。而当我们离开时,这个对象的类也就不会受到影响。利用不同的分类,可以实现不同版本的拦截或注入的方法。

 

然而,分类也有一些限制。其作用use代码块中,所以也就限定在了执行线程。注入的方法只能在use()块内使用。多次进入和退出这个块是有代价的。每次进入时,Groovy都必须检查静态方法,并将其加入到新作用的一个方法列表中。在块的最后还要清理掉该作用域。

 

如果调用不是太频繁,而且想要分类这种可控的方法注入所提供的隔离性,就可以使用分类。如果这些特性变成了限制,则可以使用ExpandoMetaClass来注入方法。

 

 

  1. ExpandoMetaClass
package mop

Integer.metaClass.daysFormNow = { ->

    Calendar today = Calendar.instance

    today.add(Calendar.DAY_OF_MONTH, delegate)

    today.time

}

println 5.daysFormNow()

Integer.metaClass.constructor << { Calendar calendar ->

    new Integer(calendar.get(Calendar.DAY_OF_YEAR))

}

println(new Integer(Calendar.instance))

 

结果:

Sun Dec 01 14:52:15 CST 2019

330

缺点,注入的方法只能在Groovy的代码里面调用…不过有变通方案。

就是在Java调用Groovy时,使用Groovy对象,在Groovy对象中重写methodMissing(String name, args)

class DynamicGroovyClass {

    def methodMissing(String name, args){

        println("You Called $name with ${args.join(', ')}.")

        args.size()

    }

}

public class CallDynamicMethod {

    public static void main(String[] args){

        groovy.lang.GroovyObject instance = new DynamicGroovyClass();

        Object result1 = instance.invokeMethod("squeak", new Object[]{});

        System.out.println("Received: "+result1);

        Object result2 = instance.invokeMethod("quack", new Object[]{"like","a","duck"});

        System.out.println("Received: "+result2);

    }

}

结果:

You Called squeak with .

Received: 0

You Called quack with like, a, duck.

Received: 3

 

  1. Mixin

package mixinmop

abstract class Writer {
    abstract void write(String message)
}


class StringWriter extends Writer {
    def target = new StringBuilder()

    @Override
    void write(String message) {
        target.append(message)
    }

    String toString() {
        target.toString()
    }
}

def writeStuff(writer) {
    writer.write("This is stupid")
    println writer
}

def create(theWriter, Object[] filters = []) {
    def instance = theWriter.newInstance()
    filters.each { filter -> instance.metaClass.mixin filter }
    instance
}

writeStuff(create(StringWriter))

class UppercaseFilter {
    void write(String message) {
        def allUpper = message.toUpperCase()

        invokeOnPreviousMixin(metaClass, "write", allUpper)
    }
}

Object.metaClass.invokeOnPreviousMixin = {
    MetaClass currentMixinMetaClass, String method, Object[] args ->
        def previousMixin = delegate.getClass()
        for (mixin in mixedIn.mixinClasses){
            if (mixin.mixinClass.theClass ==
                currentMixinMetaClass.delegate.theClass) break
            previousMixin = mixin.mixinClass.theClass
        }

        mixedIn[previousMixin]."$method"(*args)
}


writeStuff(create(StringWriter, UppercaseFilter))

class ProfanityFilter{
    void write(String message){
        def filtered = message.replaceAll('stupid', 's*****')
        invokeOnPreviousMixin(metaClass, "write", filtered)
    }
}

writeStuff(create(StringWriter, UppercaseFilter, ProfanityFilter))
writeStuff(create(StringWriter, ProfanityFilter, UppercaseFilter))




结果:

This is stupid

THIS IS STUPID

THIS IS S*****

THIS IS STUPID

 

 

方法注入(Method Injected)

就是在编写代码是就已知了方法名,想在一个类或者一系列类里添加这个方法。利用注入的方法,可以动态的将这个方法添加到对应的类中。

其中Groovy的Category、ExpandoMetaClass和mixin都是可以利用的方式。

 

方法合成(Method Synthesis)

想在调用时动态确定方法的行为,Groovy的invokeMethod、methodMissing和GroovyInterceptable对于方法合成都非常有用。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值