您是否可以使用Groovy元编程来覆盖Java类上的私有方法

我正在尝试使用元编程覆盖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类(或实例)上的方法,以便在内部调用时调用重写的实现?

Groovy as operator非常强大,可以使用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调用,而不是私有方法。

您不能使用metaClass覆盖Groovy中从Java代码调用的方法。

这就是为什么你无法在Java中“模拟”对这个私有方法的调用:它由Java类本身调用,而不是来自Groovy。

当然,如果您的类是用Groovy编写的,那么此限制将不适用。

我建议您重构Java类,如果可以的话,您可以使用常规方法来模拟昂贵的方法调用。 或者甚至使方法受到保护,然后在子类中覆盖它。

我偶然发现了这个问题,并认为我应该提供一个不同的答案:是的,您可以覆盖现有方法 – 您只需将元类更改为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时 – 它可以工作。

看来你不能使用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, // ie 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受到保护,或者最好重构它,以便它更适合测试

 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的模拟/存根实现轻松实例化它。

Interesting Posts