有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

可以使用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()方法在内部调用的,而不是通过元类调用的。如果我打电话给我的代理。doSomethingCrazyExpensive()直接调用重写的方法,因此元编程在一定程度上可以工作

有没有一种方法可以使用元编程来重写Java类(或实例)上的方法,从而在内部调用被重写的实现时调用它


共 (4) 个答案

  1. # 1 楼答案

    不能使用元类重写Groovy中从Java代码调用的方法

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

    当然,如果您的类是用Groovy编写的,那么这个限制就不适用了

    如果可以的话,我建议您重构Java类,这样您就可以使用普通方法来模拟昂贵的方法调用。或者甚至使该方法受到保护,然后在子类中重写它

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

  3. # 3 楼答案

    我无意中发现了这个问题,并认为我应该提供一个不同的答案:是的,你可以覆盖一个现有的方法——你只需要将meta类更改为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时,它就起作用了

  4. # 4 楼答案

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