Использование Groovy MetaClass для перезаписи методов

у меня есть POJO, который использует сервис для чего-то:

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);
    }
}

и у меня есть классный тестовый пример:

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 не работает, как ожидалось, но вместо этого бросает NullPointerException при попытке вызвать callX на объекте обслуживания. Я думал, что переписал doCallService метод с этой строки:

pojo.metaClass.doCallService = { String s ->

что я делаю не так?

спасибо!

3 ответов


ваш синтаксис немного выключен. Проблема в том, что pojo является объектом Java и не имеет метакласса. Для перехвата вызовов doCallService PlainOldJavaObject с помощью ExpandoMetaClass:

заменить:

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

С:

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

если ваш POJO действительно является классом Java, а не классом Groovy, то это ваша проблема. Классы Java не вызывают методы через метакласс. например, в Groovy:

pojo.publicMethod('arg')

эквивалентно этому Java:

pojo.getMetaClass().invokeMethod('publicMethod','arg');

invokeMethod отправляет вызов через метакласс. Но этот метод:

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

- это Java-метод. Он не использует invokeMethod называть doCallService. Чтобы заставить ваш код работать,PlainOldJavaObject должен быть классный класс, чтобы все вызовы проходили метакласс. Обычный Java-код не использует метаклассы.

короче говоря: даже Groovy не может переопределить вызовы метода Java, он может только переопределять вызовы из Groovy или в противном случае отправлять через invokeMethod.


то, что у вас есть, выглядит нормально. Я запустил слегка измененную версию на нем на groovy console 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 в реализации metaclass. Язык groovy движется довольно быстро, и реализация метакласса изменяется от версии к версии.

Edit-обратная связь от Комментарий:

версия groovy консоли webapp 1.7-rc-1. Похоже, что версия может работать как вы хотите. В настоящее время они находятся в RC2, поэтому я ожидаю, что он будет выпущен в ближайшее время. Не уверен, что то, что вы видите, является ошибкой или просто разницей в том, как она работает в 1.6.X версии.