你能否使用Groovy元编程来覆盖Java类中的私有方法?

4
我正在尝试使用元编程覆盖Java类中的私有方法。 代码大致如下:
// Java class
public class MyClass{

    private ClassOfSomeSort property1;
    private ClassOfSomeOtherSort property2;

    public void init(){

        property1 = new ClassOfSomeSort();
        property2 = new ClassOfSomeOtherSort();

        doSomethingCrazyExpensive();
    }

    private void doSomethingCrazyExpensive(){
        System.out.println("I'm doing something crazy expensive");
    }
}

// Groovy class
public class MyClassTest extends Specification{

    def "MyClass instance gets initialised correctly"(){

        given:
        ExpandoMetaClass emc = new ExpandoMetaClass( MyClass, false )
        emc.doSomethingCrazyExpensive = { println "Nothing to see here..." }
        emc.initialize()
        def proxy = new groovy.util.Proxy().wrap( new MyClass() )
        proxy.setMetaClass( emc )
        when:
        proxy.init()
        then:
        proxy.property1 != null
        proxy.property2 != null     
    }
}

问题在于doSomethingCrazyExpensive方法的重载实现没有被调用 - 我认为这是因为私有方法在内部被init()方法调用,而不是通过metaClass调用。如果我直接调用myProxy.doSomethingCrazyExpensive(),则会调用重载的方法,因此元编程在某种程度上确实起作用。
有没有一种方法可以使用元编程以覆盖Java类(或实例)上的方法,以便在内部调用时调用重载的实现?

也许你可以将你的类作为一个Category来“使用”,这样Category方法就可以重写元类中的方法定义。 - ludo_rj
4个回答

2

Groovy的as操作符非常强大,可以将具体类型转换为代理,其更改在Java中可见。不幸的是,它似乎无法覆盖私有方法,但我成功更改了一个公共方法:

Java类:

public class MyClass{

    public void init(){
        echo();
        doSomethingCrazyExpensive();
    }

    public void echo() { System.out.println("echo"); }

    private void doSomethingCrazyExpensive(){
        System.out.println("I'm doing something crazy expensive");
    }
}

Groovy测试:

class MyClassTest extends GroovyTestCase {
    void "test MyClass instance gets initialised correctly"(){

        def mock = [
          doSomethingCrazyExpensive: { println 'proxy crazy' },
          echo: { println 'proxy echo' }
        ] as MyClass

        mock.init()

        mock.doSomethingCrazyExpensive()
    }
}

它会打印出:
proxy echo
I'm doing something crazy expensive
proxy crazy

所以公有方法被拦截并更改了,即使从Java中调用,但私有方法不会。


1
您无法使用metaClass在Groovy中重写从Java代码调用的方法。
这就是为什么您无法在Java中“mock”对此私有方法的调用:它是由Java类本身而不是Groovy调用的。
当然,如果您的类是用Groovy编写的,则不会出现此限制。
我建议您进行Java类重构(如果可以),以便您可以使用常规方法模拟昂贵的方法调用。或者将该方法设置为protected,然后在子类中重写它。

0

我偶然遇到了这个问题,想提供一个不同的答案:是的,您可以重写现有的方法——只需将元类更改为ExpandoMetaClass。

当您添加第一个方法时,这会自动发生,例如。

以下是一个示例:

println ""
class Bob {
    String name
    String foo() { "foo" }
    void print() { println "$name = ${foo()} ${fum()}  metaclass=${Bob.metaClass}"}
    def methodMissing(String name, args) { "[No method ${name}]"  }
}

new Bob(name:"First ").print()

Bob.metaClass.fum = {-> "fum"}

new Bob(name:"Second").print()

Bob.metaClass.fum = {-> "fum"}

new Bob(name:"Third ").print()

Bob.metaClass.foo = {-> "Overriden Foo"}

new Bob(name:"Fourth").print()

结果如下:

First  = foo [No method fum]  metaclass=org.codehaus.groovy.runtime.HandleMetaClass@642a7222[groovy.lang.MetaClassImpl@642a7222[class Bob]]
Second = foo fum  metaclass=groovy.lang.ExpandoMetaClass@21be3395[class Bob]
Third  = foo fum  metaclass=groovy.lang.ExpandoMetaClass@21be3395[class Bob]
Fourth = Overriden Foo fum  metaclass=groovy.lang.ExpandoMetaClass@21be3395[class Bob]

在添加了fum方法后,您可以看到元类已更改为expando。现在,当尝试覆盖原始的foo时,它会起作用。


抱歉 - 这个问题是关于私有方法的。这不是正确的答案。 - Steve s.

0

看起来你不能使用Groovy元编程来替换Java类的方法 - 即使是公共方法 - 请尝试在Groovy控制台中执行以下操作以确认:

ArrayList.metaClass.remove = { obj ->
  throw new Exception('remove')
}

ArrayList.metaClass.remove2 = { obj ->
  throw new Exception('remove2')
}

def a = new ArrayList()
a.add('it')

// returns true because the remove method defined by ArrayList is called, 
// i.e. our attempt at replacing it above has no effect
assert a.remove('it')

// throws an Exception because ArrayList does not define a method named remove2, 
// so the method we add above via the metaClass is invoked
a.remove2('it')

如果你能修改MyClass的源代码,我会将doSomethingCrazyExpensive改为protected,或者更好地重构它,使其更适合测试。
public class MyClass {

    private ClassOfSomeSort property1;
    private ClassOfSomeOtherSort property2;
    private CrazyExpensive crazyExpensive;

    public MyClass(CrazyExpensive crazyExpensive) {
        this.crazyExpensive = crazyExpensive;
    }

    public void init(){

        property1 = new ClassOfSomeSort();
        property2 = new ClassOfSomeOtherSort();

        crazyExpensive.doSomethingCrazyExpensive();
    }
}

public interface CrazyExpensive {
    public void doSomethingCrazyExpensive();  
}

在进行了上述更改后,当测试MyClass时,您可以轻松地使用CrazyExpensive的模拟/存根实现来实例化它。

你不能使用Groovy元编程来替换Java类的方法。我不认为这是正确的。在这里检查(http://mrhaki.blogspot.com.es/2009/12/groovy-goodness-adding-or-overriding.html)。另外,出于测试目的,我个人会用Groovy来替换`Calendar.getInstance()`。 - m0skit0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接