Can you use Groovy meta programming to override a private method on a Java class

Jay Shark picture Jay Shark · Mar 27, 2014 · Viewed 7.7k times · Source

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?

Answer

Will picture Will · Mar 28, 2014

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:

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 test:

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()
    }
}

It prints:

proxy echo
I'm doing something crazy expensive
proxy crazy

So the public method got intercepted and changed, even when being called from Java, but not the private one.