【问题标题】:Can you use Groovy meta programming to override a private method on a Java class你可以使用 Groovy 元编程来覆盖 Java 类的私有方法吗
【发布时间】:2018-05-06 20:42:09
【问题描述】:

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

【问题讨论】:

  • 也许您可以将您的类“使用”为类别,然后类别方法可以覆盖元类中的方法定义

标签: java groovy overriding metaprogramming expandometaclass


【解决方案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");
    }
}

常规测试:

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 调用公共方法,也会被拦截和更改,而不是私有方法。

【讨论】:

    【解决方案2】:

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

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

    当然,如果您的课程是用 Groovy 编写的,则此限制不适用。

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

    【讨论】:

      【解决方案3】:

      我偶然发现了这个问题,并认为我应该提供一个不同的答案:是的,您可以覆盖现有方法 - 您只需将元类更改为 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 方法后,元类更改为扩展。现在,当尝试覆盖原始 foo 时 - 它可以工作。

      【讨论】:

      • 哎呀——问题是针对私有方法的。这不是正确的答案。
      【解决方案4】:

      您似乎无法使用 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 的模拟/存根实现轻松实例化它。

      【讨论】:

      • “你不能使用 Groovy 元编程来替换 Java 类的方法”我认为这是不正确的。检查here。此外,出于测试目的,我个人将 Calendar.getInstance() 替换为 Groovy 中我自己的。
      猜你喜欢
      • 2014-05-06
      • 2011-05-01
      • 2011-01-28
      • 2012-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-01
      • 2021-09-09
      相关资源
      最近更新 更多