首页 文章

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

提问于
浏览
2

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

4 回答

  • 1

    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调用,也不是私有方法 .

  • 0

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

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

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

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

  • 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时 - 它可以工作 .

  • 2

    看来你不能使用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 的模拟/存根实现轻松实例化它 .

相关问题