Groovy方法拦截,注入,合成,委托和动态类

首先说下调用方法的方式:

class Psrson{

    def name

    def dream() {
        System.out.println 'i have a dream'
    }
}
  • 第一种方式:
    def p = new Psrson(‘Test’)
    p.dream()

  • 第二种方式:
    def p = new Person(‘Test’)
    p.invokeMethod(‘dream’,null)

  • 第三种方式:
    def p = new Person(‘Test’)
    MetaMethod m = p.metaClass.getMetaMethod(‘dream’,null)
    m.invoke(p,null)

方法拦截:

Demo地址:https://github.com/zhaopingfu/listener8

  • 第一种方式:
    类实现GroovyInterceptable,并重写Object invokeMethod(String name, Object args)

    在这里每次调用该类的方法,都会先走invokeMethod方法,当然,调用一个类中不存在的方法,也会走这个方法,这个时候是不会出现MissingMethod方法的

    Object invokeMethod(String name, Object args) {
        System.out.println 'invoke'
        //是否有这个方法,有就调用,没有就来个提示
        if (metaClass.invokeMethod(this, 'respondsTo', name, args)) {
            metaClass.invokeMethod(this, name, args)
        } else {
            //do some
            System.out.println 'missing method'
        }
    }
    

    总结:当一个类实现GroovyInterceptable后,在改类的对象上调用任何方法都会先执行invokeMethod方法,在invokeMethod方法里进行方法的分发,注意不要在invokeMethod里调用printl n,因为调用println也会走进invokeMethod,导致崩溃,所以应该调用System.out.println

  • 第二种方式:使用metaClass

    • 1、在单个对象上进行拦截

      def p = new Psrson('Test')
      p.metaClass.dream = {
          System.out.println 'replace dream'
      }
      p.dream()
      
    • 2、在类上进行拦截

      Person.metaClass.dream = {
          System.out.println 'replace dream'
      }
      
      举例:
      String.metaClass.plus = {
      CharSequence i ->
          i
      }
      
      println("123" + "abc")
      

      这里修改了String上面的+语法,只要,所以打印出来的是abc

    注意:当一个类或者对象覆盖了metaClass的invokeMethod方法后,那么这就相当于这个类实现了GroovyInterceptable,在它上面调用方法都会走到invokeMethod里面来,如果重写了,要手 动的将方法进行分发

  • 第三种方式:
    重写def methodMissing(String name, def args),def propertyMissing(String name, def arg),def propertyMissing(String name)

    当在一个对象上调用类中不存在的方法或者属性,会走到上面的方法中,看方法名也可以知道是什么意思了

    def methodMissing(String name, def args) {
        println "methodMissing"
        return "没有${name}这个方法"
    }
    

方法注入:运行时元编程

Demo地址:https://github.com/zhaopingfu/listener9

  • 第一种方式.category 分类注入:灵活,可控性高,对性能有影响

    • 1、static方法的方式

      class Req {
          static def get(String self) {
              self.toURL().text
          }
      }
      
      use(Req) {
          println "https://www.baidu.com/".get()
      }
      

      这里使用use可以使用为字符串注入的get方法

    • 2、注解的方式

      class Req {
          static def get(String self) {
              self.toURL().text
          }
      }
      
      @Category(String)
      class StringUtils {
          def get() {
              toString()
          }
      
          def toUpperCase() {
              'toUpperCase'
          }
      }
      
      //谁写在后面执行谁的get方法
      use(Req, StringUtils) {
          println "https://www.baidu.com/".get()
      }
      

      假如字符串已经有了toUpperCase方法,我们在StringUtils里面也有一个toUpperCase方法,那么在use里调用toUpperCase会调用谁的呢?是这样的,先在StringUtils里面找,如果有就执行,没有就去Req里面找,Req里面也没有,才回去String本身里面去找

  • 第二种方式.expandoMetaclass

    使用metaClass注入方法可以使用xxx.metaClass.xx <<{} 和xxx.metaClass.xx = {},这里我们推荐使用”=”,因为如果一个类中已经有了一个方法,再使用”<<”注入的话,就会报错

    • 1、注入对象方法

      str.metaClass.get = {
          println delegate
          delegate.toString().toURL().text
      }
      def str = "https://www.baidu.com"
      println str.get()
      
      def str1 = new String("https://www.baidu.com")
      println str1.get()
      "https://www.baidu.com".get()
      println "https://www.baidu.com".get()
      

    这里的str1调用get方法是会报错的,因为他是new出来的,而其他的jvm中已经帮我们做了优化,为了节省内存,因为他们都是一样的,所以他们都可以调用get方法

    • 2、注入静态方法

      String.metaClass.'static'.printlnClass = {
          println "================="
          println delegate
      }
      "www.baidu.com".printlnClass()
      String.printlnClass()
      

    和普通方法类似,只要在前面加入一个’static’就可以了,注入静态方法这里直接在String上面,所以可以直接使用

    • 3、注入构造方法

      String.metaClass.constructor = {
          Calendar calendar ->
              new String(calendar.getTime().toString())
      }
      println new String(Calendar.instance)
      

      注入构造方法和注入普通方法类似,只不过方法名不能随便起了

    • 4、上面的方式都有点乱,那么有没有统一注入方法呢,当然有

      String.metaClass {
          get = {
              delegate.toString().toURL().text
          }
          'static' {
              printlnClass = {
                  println "================="
                  println delegate
              }
          }
          constructor = {
              Calendar calendar ->
                  new String(calendar.getTime().toString())
          }
      }
      def str2 = "https://www.baidu.com"
      println str2.get()
      str2.printlnClass()
      println new String(Calendar.instance)
      

    上面这种写法和前面的结果一样,只是整齐了一点

    • 5、ExpandoMetaClass的方式

      println String.metaClass
      //这里的String是准备要给那个类型注入方法
      def emc = new ExpandoMetaClass(String)
      emc.get = {
          delegate.toString().toURL().text
      }
      //先初始化,初始化之后才会生效
      emc.initialize()
      println String.metaClass.class
      //修改String的metaClass
      String.metaClass = emc
      println String.metaClass.class
      println "https://www.baidu.com".get()
      String.metaClass = null
      

    这里调用字符串的get是可以成功的

    注意下

    假如有个java类

    public class Test {
    
        public void work() {
            run();
        }
    
        public void run() {
            System.out.println("run");
        }
    }
    

    之后我们在groovy中动态的修改

    Test.metaClass.run = {
        println 'groovy run'
    }
    new Test().run()
    

    上面这么写是可以修改的,但是new Test().work()这么写是修改不了的,因为调用work方法是通过静态节点去一层一层调用,但是在work里面调用run()不是动态节点的方式,所以这里调用work()打印出来的还是run

  • 第三种方式.使用mixin

    mixin和第一种方式基本上一样

    @Mixin(String)
        class Get {
            def get(String url) {
                println 'Get'
                url.toURL().text
            }
        }
    
    class Post {
        def get(String url) {
            println 'Post'
            url.toURL().text
        }
    }
    
    new Get().substring(0)
    
    //混合注入方法
    String.mixin(Get,Post)
    //往metaClass中混合也是一样的
    String.metaClass.mixin(Get)
    println "".get('https://www.baidu.com/')
    

    这里先调用谁的get方法,也跟第一种方式一样,调用的是最新的那个(最后加入的)

动态类:Expando

Demo地址:https://github.com/zhaopingfu/listener10

def expando = new Expando(name: 'hello', fun1: { "fun1" })
expando.height = 100
expando.fun2 = {
    "fun2"
}

println expando.name
println expando.height
println expando.fun1()
println expando.fun2()

方法合成:

Demo地址:https://github.com/zhaopingfu/listener10

class Person {
    def methodMissing(String name, def args) {
        println 'missing'
        if (name.startsWith('play')) {
            //生成的class文件,调用方式不一样
//            printf metaClass
            Person p = this
//            println p.metaClass
            p.metaClass."$name" = {
                println "invoke $name"
            }
            "$name"(args)
        }
        return null
    }
}

def p = new Person()
println p.metaClass
p.playGame()
p.playGame()
p.playGame()

刚开始调用playGame方法,不存在会进入methodMissing方法,然后一看是play开头的,然后动态的合成一个方法,然后调用,之后再调用playGame的时候,因为已经注入了playGame方法了,就不会再进入methodMissing方法了

方法委托:

Demo地址:https://github.com/zhaopingfu/listener10

  • 第一种方式:手动来

    class Work1 {
        def execute1() {
            println "execute1"
        }
    }
    
    class Work2 {
        def execute2() {
            println "execute2"
        }
    }
    
    class WorkManager {
        Work1 work1 = new Work1()
        Work2 work2 = new Work2()
        Work2 work3 = new Work2()
    
        def methodMissing(String name, def args) {
            WorkManager wm = this
            if (work1.respondsTo(name, args)) {
                wm.metaClass."$name" = {
                    work1.invokeMethod(name, it)
                }
                "$name"(args)
            } else if (work2.respondsTo(name, args)) {
                wm.metaClass."$name" = {
                    work2.invokeMethod(name, it)
                }
                "$name"(args)
            }
            return null
        }
    }
    
    def wm = new WorkManager()
    wm.work1.execute1()
    wm.execute1()
    

    Work1和Work2将方法委托给WorkManager,通过WorkManager来调用方法

  • 第二种方式:简化的手动

    class WorkManager1 {
    
        {
            delegate(Work1, Work2)
        }
    
        def delegate(Class... classes) {
            //创建对应的对象
            def objects = classes.collect { it.newInstance() }
            WorkManager1 wm = this
            //注入methodMissing方法
            wm.metaClass.methodMissing = {
                String name, def args ->
                    //查找调用的方法的实现对象
                    def object = objects.find { it.respondsTo(name, args) }
                    if (object) {
                        //动态注入方法
                        wm.metaClass."$name" = {
                            object.invokeMethod(name, it)
                        }
                        "$name"()
                    }
            }
        }
    }
    
    def wm1 = new WorkManager1()
    wm1.execute1()
    wm1.execute2()
    

    上面那种方式,每次委托一个对象,都要加一个if else,而这里只需要在静态代码块里天价一下就好了

  • 第三种方式:注解

    class WorkManager2 {
        @Delegate
        Work1 work1 = new Work1()
        @Delegate
        Work2 work2 = new Work2()
    }
    
    new WorkManager2().execute1()
    new WorkManager2().execute2()   
    

    这里Groovy帮我们做了一个注解,使用这个注解自动的帮我们进行委托,代码提示也有了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值