groovy replace method

[TOC]

Groovy 动态代理即替换方法

我们知道在Java中可以使用以下几种方法替换方法。

  1. JDK自带的动态代理只支持修改某个类所实现的接口的方法。java只支持单继承,所有的代理类都是Proxy的子类,所以只能覆盖接口的方法。
  2. asm直接修改字节码,直接修改字节码,直接修改类的方法和所实现接口的方法。
  3. javassist直接修改字节码,直接修改类的方法和所实现接口的方法。
  4. cglib基于asm封装,直接修改类的方法和所实现接口的方法。

总的来说,使用jdk自带的方案有局限,使用第三方框架能够支持全部功能。下面我们就来看一下,groovy作为一门jvm语言如何更加方便的实现动态代理的。

1.1 演示类

class ReplaceMethod {
    
    // 用于演示替换实例方法
    public List<String> getStreamInputs() {
        List<String> strings = "a, b, c, d".split(", ");
        return strings;
    }

    // 用于演示替换泛型方法,同时演示如何区分同名的方法。
    public void genericMethod(List<String> strings) {
        println strings
    }

    public void genericMethod(List<String> strings, boolean num) {
        println strings
    }

}   

2 如何替换方法?

  1. 通过metaClass的pickMethod获取到原始的方法引用。
  2. 使用闭包替换metaClass上要替换的方法即可。

样例代码如下:

def repalceMethod = new ReplaceMethod();
println repalceMethod.getStreamInputs();
def oldMethod = ReplaceMethod.metaClass.&pickMethod('getStreamInputs', (Class[]) null)
repalceMethod.metaClass.getStreamInputs = {
    println "replaced"
    // 执行旧方法.
    println oldMethod.invoke(repalceMethod);
    List<String> strings = "1, 2, 3, 4".split(", ");
    return strings;
}
println repalceMethod.getStreamInputs()

2.1 通过class的meta直接替换方法

Class.forName('com.coffee.groovy.metaclass.ReplaceMethod').metaClass.invokeMethod = { name, args ->
    println "replaced: Called ${name} with ${args}"
    println args.length
    println args.class
    println args.metaClass
    // http://stackoverflow.com/questions/10125903/groovy-overriding-invokemethod-for-a-single-instance
    delegate.class.metaClass.getMetaMethod(name, args)?.invoke(delegate, args)
}

2.2 替换泛型方法&替换同名方法

通过指定要替换的方法的参数类型,既可以通过pickMethod获取到对应的正确函数。对泛型而言,无需指定其具体的类型。

List<Class> argClasses = [];
argClasses.add(List.class)
argClasses.add(boolean.class)
println argClasses
def clazz = Class.forName('com.coffee.groovy.metaclass.ReplaceMethod');
def oldGenericMethod = clazz.metaClass.&pickMethod('genericMethod',
        argClasses.toArray(new Class[0]))
clazz.metaClass.genericMethod = { strings, result ->
    println "replaced: $strings : $result"
}

repalceMethod.genericMethod("ac, sd, dc, dd".split(", "), true)