Android Gradle的神奇之处 ---- Groovy元编程

对于元编程,这个概念可能比较陌生,如果说下面的这个场景,就会逐步引入元编程的思想:Java中的反射机制,通过反射拿到构造方法,调用类中的方法,属性,invoke;但是反射不能做的就是,能够在运行的时候,改变类的行为,比如往类中添加属性或者方法

当前以上的情景,使用字节码插桩技术(javassist、ASM等)实现,修改class文件,但是Groovy元编程就能够原生地实现这项技术

1 MOP协议

MOP,Meta Object Protocol,元对象协议,如果Groovy类中没有某个方法,那么就可以使用MOP元编程的方式,在运行的时候,将某个方法注入,动态调用这个方法,这个方法在源码中是看不到的

2 MOP的实现方式

2.1方法拦截

方法拦截,类似于Android中的Hook技术,在调用某个方法的时候,先捕获这个方法,然后改变这个方法执行的方式 ,在Groovy中,通过实现GroovyInterceptable,实现invokeMethod方法

class GroovyTest02 implements GroovyInterceptable{

    def func(){
        System.out.println("func")
    }

    @Override
    Object invokeMethod(String name, Object args) {

        System.out.println("$name invokeMethod")
        
    }

    static void main(String[] args) {


        new GroovyTest02().func()  // func invokeMethod
    }
}

这个时候,调用func方法将不会再走func这个方法,而是走invokeMethod,其中name就是执行的func方法的名字,这也是AOP面向切面编程的思想

2.1.1 responseTo

在使用invokeMethod执行一个方法的时候,如何判断这个方法是否存在,就是使用responseTo来判断,这个方法返回一个方法列表,如果有这个方法,那么列表就不为空

@Override
Object invokeMethod(String name, Object args) {

    System.out.println("$name invokeMethod")
    respondsTo(name)
}

注意这里不能这么用,注解调用会造成无限递归造成栈溢出,应该使用metaClass

@Override
Object invokeMethod(String name, Object args) {
    System.out.println("$name invokeMethod")
    if(metaClass.invokeMethod(this,"respondsTo",name,args)){
        System.out.println("$name 方法存在")
        System.out.println("$name 方法执行前")
        metaClass.invokeMethod(this,name,args)
    }
}
func invokeMethod
func 方法存在 [28]
func 方法执行前
I'm 28 岁了

如果判断当前方法存在了,那么就可以执行invokeMethod方法,那么我在方法之前,就可以加上自己的业务逻辑

2.1.2 metaClass

因为GroovyInterceptable是实现了GroovyObject,而一个类也是实现了GroovyObject,那么可以直接拿到这个类的metaClass,做方法拦截

class GroovyTest03 {

    def func() {
        println("这是一个方法")
    }

    static void main(String[] args) {
        def test = new GroovyTest03()
        test.metaClass.func = {
            //这里会覆盖已有方法的内容
            println("这是被覆盖的一个方法")
        }
        test.func()
    }
}

输出:这是被覆盖的一个方法

当拿到这个类的metaClass之后,如果拿到的方法没有,那么就会注入一个方法;如果当前方法已经存在了,那么传入的闭包就会覆盖之前方法

test.metaClass.func2 = {
    //这里动态注入的一个方法
    println("这是动态注入的一个方法")
}
test.func2()

那么既然这样能够覆盖已知的方法,那么在2.1.1中的invokeMethod同样也可以覆盖

def test = new GroovyTest03()
test.metaClass.invokeMethod = { name,parmas->
    //这里会覆盖已有方法的内容
    println("$name 方法被拦截")
}
test.func()

//这里又创建了几个类
new GroovyTest03().func()
new GroovyTest03().func()
new GroovyTest03().func()

func方法被拦截了
这是一个方法
这是一个方法
这是一个方法

这里其实跟实现了 GroovyInterceptable接口是一样的效果;这里只是针对test一个对象的方法拦截,如果想要对所有的对象进行拦截,那么就直接对这个GroovyTest03类的metaClass进行处理即可

GroovyTest03.metaClass.invokeMethod = { name,parmas->
    //这里会覆盖已有方法的内容
    println("$name 方法被拦截")
}

那么既然invokeMethod能够拦截到所有的方法,那么对于String类中的toString拦截做一下修改,也是可以的

String.metaClass.invokeMethod = {
    name,params->
        def method = getMetaClass().getMetaMethod(name,args)
        if(method != null && method.name == 'toString'){
            'toString 被修改了'
        }
}

2.2 方法注入

在Kotlin中,是能够对一个类进行扩展的,这样的灵活性也非常便利;而在Groovy中,如果要对一个类进行扩展,可以使用方法注入的方式来实现

2.2.1 metaClass注入

在前面中,使用metaClass可以实现注入,如果当前类中没有这个方法,就会注入一个方法

def test = new GroovyTest03()
test.metaClass.func1 = {
    System.out.println("新注入方法 func1")
}
test.func1()

新注入方法 func1

打印一下,注入前后,metaClass有什么变化

[groovy.lang.MetaClassImpl@8909f18[class GroovyTest03]]
[groovy.lang.ExpandoMetaClass@60704c[class GroovyTest03]]

可以看到,在注入之后,metaClass变为了ExpandoMetaClass,这就是第2种注入方式

2.2.2 ExpandoMetaClass注入

def emc = new ExpandoMetaClass(GroovyTest03)
emc.func1 = {
    println("这是注入的一个方法")
}
emc.initialize()
GroovyTest03.metaClass = emc
new GroovyTest03().func1()

通过创建一个ExpandoMetaClass对象,其中传入的参数就是要扩展注入的类;最终当前的ExpandoMetaClass对象就赋值给扩展类的metaClass

2.2.3 分类注入

首先写一个工具类,用来定义某个方法,这也是在Java中常用的方式

static class StringUtils{

    static def isEmpty(String self){
        println("StringUtils isEmpty")
        self == null || self.length() == 0
    }
}

那么如何在对象中,调用这个扩展方法,使用use

use(StringUtils){
    "123".isEmpty()
}

即便是在String类中有一个isEmpty方法,但是这样使用最终还是走的是StringUtils中方法;如果还有一个类中,存在相同的方法

static class StringUtils2{
    static def isEmpty(String self){
        println("StringUtils isEmpty2")
        self == null || self.length() == 0
    }
}
use(StringUtils,StringUtils2){
    "123".isEmpty()
}

那么最终会调用的是后面StringUtils2中的isEmpty方法

上面的方式,每次都需要定义一个静态方法,还要另外一种写法,不需要使用静态方法

@Category(String)
static class StringUtils{
     def isEmpty(){
        println("StringUtils isEmpty")
        this == null || this.length() == 0
    }
}

使用Category注解,表明当前工具类中针对那个类做的扩展方法,isEmpty只能为String类提供扩展方法,使用this关键字就能代替入参

2.3 属性注入

属性注入跟方法注入稍微有点出入,Groovy并没有给出特定的api来注入新的参数,可以使用拦截属性的api来完成参数注入

class Person{
    def username
}

def person = new Person()
println person.age

如果像上面的访问方式访问age属性肯定是访问不到的,通过propertyMissing拦截属性,给属性赋值默认值,或者直接给属性赋值

class Person{
    def username

    def propertyMissing(String name){
        println "拦截到了属性 $name"
        19
    }
}

如果在代码中,选择给没有的属性赋值,则会走下面这个方法

def propertyMissing(String name,def args){
    println "拦截到了属性 $name 参数 $args"
    args
}
person.age = 21

拦截到了属性 age 参数 21

有propertyMissing,也会有methodMissing,当然这种只是为了做特殊处理,防止报错,并不能达到赋值的效果

3 Groovy语法的补充

3.1 快速取值

如果一个字符串,通过某个字符分割,往常都是通过split函数分割,得到一个char数组,然后从数组中根据index取出数据,那么Groovy怎么快捷取出数据呢?

def str = "com.google.bbb:appcompact:1.1.0"
def (group,name,version) = str.split(":")
println group
println name
println version

在Kotlin中也有对应的取值方式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Awesome_lay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值