我试图使用元编程覆盖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类(或实例)上的方法,以便在内部调用时调用重写的实现?
答案 0 :(得分: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 :(得分:1)
您无法使用metaClass覆盖Groovy中从Java代码调用的方法。
这就是为什么你不能"模拟"在Java中调用这个私有方法:它由Java类本身调用,而不是来自Groovy。
当然,如果您的课程是用Groovy编写的,那么此限制将不适用。
我建议您重构Java类,如果可以的话,您可以使用常规方法来模拟昂贵的方法调用。或者甚至使方法受到保护,然后在子类中覆盖它。
答案 2 :(得分: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
受到保护,或者最好重构它,以便它更适合测试
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
的模拟/存根实现轻松实例化它。
答案 3 :(得分: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时 - 它可以工作。