[TOC]

Groovy :MOP与元编程

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

*元编程(metaprogramming)*意味着编写能够操作程序的程序,包括操作程序自身.像Groovy这样的动态语言通过元对象协议(MetaObject Protocal, MOP)提供了这种能力.利用Groovy的MOP,创建类,编写单元测试和引入模拟对象都是很容易的.

在Groovy中,使用MOP可以动态的调用方法,甚至可以在运行时合成类和方法.该特性让我们有这种感觉:对象顺利地修改了它的类.

讨论两个方面:Groovy对象的组成和Groovy如何解析Java对象和Grooy对象的方法调用.

探索元对象

Groovy 对象

Groovy对象是带有附加功能的Java对象.在Groovy中,Groovy对象比编译好的Java对象具有更多的动态行为.

在一个Groovy应用中,我们会使用三类对象:POJO,POGO和Groovy拦截器.

查询方法和属性

调用一个方法

str = "hello"
methodName = 'toUpperCase'

methodOfInterest = str.metaClass.getMetaMethod(methodName)
println methodOfInterest.invoke(str)

respondsTo方法判断是否存在方法

str = "hello"
println String.metaClass.respondsTo(str,'toUpperCase') ? 'yes' : 'no'  //yes
println String.metaClass.respondsTo(str,'compareTo',"test") ? 'yes' : 'no' //yes
println String.metaClass.respondsTo(str,'toUpperCase', 5) ? 'yes' : 'no' //no

动态的访问对象

除了前面介绍的方法和属性的查询方式和动态调用方式,Groovy还有其他比较方便的访问属性和调用方法的方式.一下为例子.

def printInfo(obj){
    usrRequestedProperty = 'bytes'
    usrRequestMethod = 'toUpperCase'
    
    println obj[usrRequestedProperty]
    println obj."$usrRequestedProperty"
    
    println obj."$usrRequestMethod"()
    println obj.invokeMethod(usrRequestMethod,null)
}

printInfo 'hello'
[104, 101, 108, 108, 111]
[104, 101, 108, 108, 111]
HELLO
HELLO

要迭代一个对象的所有属性,我们可以使用properties属性(或者getter)

如下:

'hello'.properties.each {
    println it
}

使用MOP拦截方法

在Groovy中可以非常方便的实现AOP编程,比如方法拦截或者方法建议.before advice | after advice | around advice.

使用GroovyInterceptable拦截方法

如果一个Groovy对象实现了GroovyInterceptable接口,那么当调用改对象上的任何一个方法时,不管是存在的还是不存在的,被调用的都将是invokeMethod().也就是说,GroovyInterceptable的invokeMethod方法劫持了该对象上的所有方法调用

如果想要实现环绕建议,只需要在这个方法内实现我们的逻辑.然而想实现前置逻辑或后置建议或者both,我们首先要实现自己的前置/后置逻辑,然后在恰当的时机路由到真实的方法,要路由调用,我们将使用MetaMethod,它可以从MetaClass获得

ps:

对于其他Groovy对象(没有实现上述接口的,只有调用到不存在的方法的时候才会调用该方法.有个例外,如果我们在一个对象的MetaClass上实现了invokeMethod不管方法存在与否,都会调用到该方法.

例子:

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.println "Called 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 (e){
    println e
}
Called to start intercepted
running filtercheck called ...
start called ...
Called to drive intercepted
running filtercheck called ...
drive called
Called to check intercepted
check called ...
Called to speed intercepted
running filtercheck 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(), inspect()

使用MetaClass拦截方法

使用GroovyInterceptable拦截了方法的调用,这种方式适合拦截作者是我们自己的类中的方法.然而,如果我们无权修改类的源代码,或者这个类是个Java类,就行不通了,此外,我们也可能在运行时决定基于某些条件或应用状态开始拦截调用,对于这几种情况,我们可以在MetaClass上实现invokeMethod()方法,并以此来拦截方法.

class Car{
    def check(){
        System.out.println("check called ...")
    }
    
    def start(){
        System.out.println("start called ...")
    }
    
    def drive(){
        System.out.println "drive called"
    }
}

Car.metaClass.invokeMethod = {
    String name, args ->
        System.out.println("Call to $name intercepted ... ")
        if (name != 'check'){
            System.out.print"Running filter"
            Car.metaClass.getMetaMethod('check').invoke(delegate,null)
        }
        
        def validMethod = Car.metaClass.getMetaMethod(name,args)
        if (validMethod != null){
            validMethod.invoke(delegate,args)
        }else {
            Car.metaClass.invokeMissingMethod(delegate,name,args)
        }
}

car = new Car()

car.start()
car.drive()
car.check()

try {
    car.speed()
}catch (e){
    println e
}

Comments

2017-08-28

⬆︎TOP