使用Groovy元类覆盖方法

使用Groovy元类覆盖方法

问题描述:

我有一个使用服务做的东西POJO:使用Groovy元类覆盖方法

public class PlainOldJavaObject { 

    private IService service; 

    public String publicMethod(String x) { 
     return doCallService(x); 
    } 

    public String doCallService(String x) { 
     if(service == null) { 
      throw new RuntimeException("Service must not be null"); 
     } 
     return service.callX(x); 
    } 

    public interface IService { 
     String callX(Object o); 
    } 
} 

而且我有一个Groovy测试用例:

class GTest extends GroovyTestCase { 

    def testInjectedMockIFace() { 
     def pojo = new PlainOldJavaObject(service: { callX: "very groovy" } as IService) 
     assert "very groovy" == pojo.publicMethod("arg") 
    } 

    def testMetaClass() { 
     def pojo = new PlainOldJavaObject() 
     pojo.metaClass.doCallService = { String s -> 
      "no service" 
     } 
     assert "no service" == pojo.publicMethod("arg") 
    } 
} 

第一个测试方法,testInjectedMockIFace作品如预期的那样:POJO创建的动态实现为IService。当调用callX时,它仅返回“非常常见”。这样,服务就被嘲笑了。

但是我不明白为什么第二种方法testMetaClass不能按预期方式工作,而是在尝试调用服务对象上的callX时抛出NullPointerException。我想我已经覆盖了doCallService方法这一行:

pojo.metaClass.doCallService = { String s -> 

我在做什么错?

谢谢!

您的语法稍微偏离。问题是pojo是一个Java对象,并没有metaClass。为了拦截PlainOldJavaObject的doCallService使用ExpandoMetaClass电话:

只需更换:

pojo.metaClass.doCallService = { String s -> 
     "no service" 
    } 

有了:

PlainOldJavaObject.metaClass.doCallService = { String s -> 
     "no service" 
    } 
+3

这里要记住的一件事是,当你操作类的metaClass,从这个点向前的每个实例将被操纵。这可能会对同一会话中运行的其他测试产生重大影响。当您操作某个类的实例时,只有该实例受到影响。 – 2013-05-06 15:58:17

你看起来不错。我在groovy控制台webapp上运行了一个稍微修改过的版本,并且运行没有问题。请参阅http://groovyconsole.appspot.com/以了解使用此代码的情况。

public interface IService { 
    String callX(Object o); 
} 

public class PlainOldJavaObject { 

    private IService service; 

    public String publicMethod(String x) { 
     return doCallService(x); 
    } 

    public String doCallService(String x) { 
     if(service == null) { 
      throw new RuntimeException("Service must not be null"); 
     } 
     return service.callX(x); 
    } 
} 

def pojo = new PlainOldJavaObject() 
pojo.metaClass.doCallService = { String s -> 
    "no service" 
} 
println pojo.publicMethod("arg") 

您正在使用什么版本的Groovy。这可能是元类实现中Groovy中的一个错误。 groovy语言移动速度非常快,并且元类实现从版本变为版本。

编辑 - 反馈的评论:

Groovy的控制台web应用程序的版本是1.7 RC-1。所以它看起来像这个版本可能会按照你的想法工作。他们目前在RC2中,所以我预计它很快就会发布。不确定你看到的是一个错误还是仅仅是它在1.6.x版本中的工作方式上的差异。

+0

嗨克里斯,我运行的Groovy版本:1.6.5 JVM:1.6.0_13 – raoulsson 2009-12-18 12:45:00

+0

版本与它无关。问题是doCallService(x)是Java代码,而不是Groovy代码,所以它不是metaClass。 – noah 2009-12-28 21:01:03

如果你的POJO真的是Java类,而不是Groovy类,那么这是你的问题。 Java类不通过metaClass调用方法。例如,在Groovy:

pojo.publicMethod('arg') 

相当于该Java:

invokeMethod发送通过元类呼叫。但是这种方法:

public String publicMethod(String x) { 
    return doCallService(x); 
} 

是一种Java方法。它不使用invokeMethod拨打doCallService。为了让你的代码正常工作,PlainOldJavaObject需要是一个Groovy类,这样所有的调用都会通过metaClass。普通的Java代码不使用元类。

简而言之:即使Groovy不能覆盖Java方法调用,它只能覆盖来自Groovy的调用或者通过invokeMethod调度。

+0

如何正确区分Groovy代码和Java代码?你将如何使PlainOldGroovyObject而不是PlainOldJavaObject? – Tomato 2012-12-13 10:36:44

+4

如果它位于.groovy文件中,则它是Groovy类。 – noah 2012-12-13 15:37:09

+0

明白了。感谢澄清:) – Tomato 2012-12-13 17:52:24