I'm trying to override a private method on a Java class using meta programming. The code looks something like this:
// 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
}
}
The problem is that the overridden implementation of doSomethingCrazyExpensive isn't called - I think that this is because the private method is called by the init() method internally and not called through the metaClass. If I call myProxy.doSomethingCrazyExpensive() directly, the overridden method is invoked, so the meta-programming does work to some degree.
Is there a way to use meta programming to override a method on a Java class (or instance) in such a way that the overridden implementation is called when it is invoked internally?
I stumbled on this question and thought I should provide a different answer: Yes you can override an existing method - you just have to change the meta class to ExpandoMetaClass.
This happens automatically when you add your first method, for example.
Here's an example:
The results are:
You can see after the fum method was added the meta class changed to an expando. Now when the attempt is made to override the original foo - it works.
It seems you can't use Groovy metaprogramming to replace methods of Java classes - even public methods - try the following in the Groovy console to confirm:
If you can modify the source code of
MyClass
, I would either makedoSomethingCrazyExpensive
protected, or preferably, refactor it so that it's more test-friendlyAfter making the changes above, when testing
MyClass
you can easily instantiate it with a mock/stub implementation ofCrazyExpensive
.Groovy
as
operator is quite powerful, and can create proxies out of concrete types whose changes are visible in Java. Sadly, seems like it can't override private methods, though i managed to change a public method:Java class:
Groovy test:
It prints:
So the public method got intercepted and changed, even when being called from Java, but not the private one.
You cannot override a method called from Java code in Groovy using metaClass.
That's why you won't be able to "mock" the call to this private method in Java: it is being called by the Java class itself, not from Groovy.
This limitation wouldn't apply, of course, if your class was written in Groovy.
I would suggest that you refactor the Java class if you can so that you can use normal means to mock the expensive method call. Or even make the method protected, then override it in a sub-class.