你可以使用 Groovy 元编程来覆盖 Java 类的私有方法吗
Posted
技术标签:
【中文标题】你可以使用 Groovy 元编程来覆盖 Java 类的私有方法吗【英文标题】:Can you use Groovy meta programming to override a private method on a Java class 【发布时间】: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 类(或实例)上的方法,从而在内部调用被覆盖的实现时调用它?
【问题讨论】:
也许您可以将您的类“使用”为类别,然后类别方法可以覆盖元类中的方法定义 【参考方案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 中我自己的。以上是关于你可以使用 Groovy 元编程来覆盖 Java 类的私有方法吗的主要内容,如果未能解决你的问题,请参考以下文章
GroovyGroovy 语言特点简介 ( 支持 Java 语法 | 支持 Java 虚拟机 | Groovy 语言是动态语言 | Groovy 扩展 JDK | 编译时元编程 )
Groovy编译时元编程 ( 编译时元编程引入 | 声明需要编译时处理的类 | 分析 Groovy 类的 AST 语法树 )
GroovyMOP 元对象协议与元编程 ( 使用 Groovy 元编程进行函数拦截 | 重写 MetaClass#invokeMethod 方法实现函数拦截 | 实现函数调用转发 )