Использование 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);
}
}
И у меня есть тестовый пример 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
, он просто возвращает "very groovy". Таким образом, услуга издевается.
Однако я не понимаю, почему второй метод testMetaClass
работает не так, как ожидалось, но вместо этого генерирует исключение NullPointerException при попытке вызвать callX
в объекте службы. Я думал, что с помощью этой строки был перезаписан метод doCallService
:
pojo.metaClass.doCallService = { String s ->
Что я делаю неправильно?
Спасибо!
Ответы
Ответ 1
Ваш синтаксис - это маленький бит. Проблема в том, что pojo является Java-объектом и не имеет метакласса. Чтобы перехватить вызовы в PlainOldJavaObject doCallService с помощью ExpandoMetaClass:
Просто замените:
pojo.metaClass.doCallService = { String s ->
"no service"
}
С
PlainOldJavaObject.metaClass.doCallService = { String s ->
"no service"
}
Ответ 2
Если ваш POJO действительно является классом Java, а не классом Groovy, то это ваша проблема. Классы Java не вызывают методы через metaClass. например, в Groovy:
pojo.publicMethod('arg')
эквивалентен этой Java:
pojo.getMetaClass().invokeMethod('publicMethod','arg');
invokeMethod
отправляет вызов через metaClass. Но этот метод:
public String publicMethod(String x) {
return doCallService(x);
}
- метод Java. Он не использует invokeMethod
для вызова doCallService
. Чтобы ваш код работал, PlainOldJavaObject
должен быть классом Groovy, чтобы все вызовы проходили через metaClass. Обычный Java-код не использует метаклассы.
Вкратце: даже Groovy не может переопределять вызовы методов Java, он может только отменять вызовы из Groovy или иначе отправлять через invokeMethod.
Ответ 3
То, что у вас хорошо выглядит. Я запускал слегка измененную версию на консоли 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 console webapp - 1.7-rc-1. Таким образом, похоже, что эта версия может работать так, как вы этого хотите. В настоящее время они находятся в RC2, поэтому я ожидаю, что он скоро будет выпущен. Не уверен, что то, что вы видите, это ошибка или просто разница в том, как она работает в версии 1.6.x.