Toda classe em Groovy implementa a interface GroovyObject:
// GroovyObject.java
public interface GroovyObject {
public Object invokeMethod(String name, Object args);
public Object getProperty(String property);
public void setProperty(String property, Object newValue);
public MetaClass getMetaClass();
public void setMetaClass(MetaClass metaClass);
}
// script.groovy
class MyClass {
}
assert GroovyObject.isAssignableFrom(MyClass.class)
Essa a base para a meta-programação (meta-programming) que acontece em tempo de execução.
Quando a classe Groovy não implementa, explicitamente, métodos como invokeMethod ou getProperty, implementações padrões são usadas. As implementações padrões de invokeMethod e get/setProperty delegam todas as chamadas de métodos para a instância de MetaClass retornada por GroovyObject.getMetaClass(). A implementação padrão desse método, por sua vez, obtém a instância de MetaClass a partir do MetaClassRegistry. A MetaClass obtida é usada para determinar quais são os métodos e as propriedades disponíveis em cada classe e é ela quem realizará a invocação “real” desses métodos ou o acesso às propriedades.
Visualizando o quadro completo, teremos que, para cada invocação de método em seu código Groovy,
'My text'.doSomethingUnusual() myObj.run()
será gerado bytecode para uma chamada ao ScriptBytecodeAdapter (por exemplo, para o método invokeMethodN)
public static Object invokeMethodN(Class senderClass, Object receiver,
String messageName,
Object[] messageArguments) throws Throwable {
try {
return InvokerHelper.invokeMethod(receiver, messageName,
messageArguments);
} catch (GroovyRuntimeException gre) {
throw unwrap(gre);
}
}
e o InvokerHelper decidirá como será obtida a MetaClass para cada caso
/**
* Invokes the given method on the object.
*/
public static Object invokeMethod(Object object, String methodName,
Object arguments) {
if (object == null) {
object = NullObject.getNullObject();
//throw new NullPointerException("Cannot invoke method " + methodName + "() on null object");
}
// if the object is a Class, call a static method from that class
if (object instanceof Class) {
Class theClass = (Class) object;
MetaClass metaClass = metaRegistry.getMetaClass(theClass);
return metaClass.invokeStaticMethod(object, methodName,
asArray(arguments));
}
// it's an instance; check if it's a Java one
if (!(object instanceof GroovyObject)) {
return invokePojoMethod(object, methodName, arguments);
}
// a groovy instance (including builder, closure, ...)
return invokePogoMethod(object, methodName, arguments);
}
static Object invokePojoMethod(Object object, String methodName, Object arguments) {
MetaClass metaClass = InvokerHelper.getMetaClass(object);
return metaClass.invokeMethod(object, methodName, asArray(arguments));
}
static Object invokePogoMethod(Object object, String methodName, Object arguments) {
GroovyObject groovy = (GroovyObject) object;
boolean intercepting = groovy instanceof GroovyInterceptable;
try {
// if it's a pure interceptable object (even intercepting toString(), clone(), ...)
if (intercepting) {
return groovy.invokeMethod(methodName, asUnwrappedArray(arguments));
}
//else try a statically typed method or a GDK method
return groovy.getMetaClass().invokeMethod(object, methodName, asArray(arguments));
} catch (MissingMethodException e) {
if (e instanceof MissingMethodExecutionFailed) {
throw (MissingMethodException) e.getCause();
} else if (!intercepting && e.getMethod().equals(methodName) && object.getClass() == e.getType()) {
// in case there's nothing else, invoke the object's own invokeMethod()
return groovy.invokeMethod(methodName, asUnwrappedArray(arguments));
} else {
throw e;
}
}
}
Agora vejamos alguns exemplos do que pode ser realizado, em termos de meta-programação, através da implementação da interface GroovyObject:
-
Responder, dinamicamente, à invocação de métodos:
class MyClass { public void method1() { println 'method1()' } @Override public Object invokeMethod(String name, Object args) { println "invokeMethod ${name}(${args})" } } MyClass c = new MyClass() c.method1() c.method2() c.method3(12, false) -
Responder, dinamicamente, à invocação de métodos e interceptar as chamadas aos métodos existentes:
class MyClass implements GroovyInterceptable { public String method1() { return 'method1 result' } @Override public Object invokeMethod(String name, Object args) { MetaClass metaClass = this.metaClass MetaMethod metaMethod = null if (metaClass.respondsTo(this, name)) { metaMethod = metaClass.getMetaMethod(name) return "intercepting ${metaMethod.doMethodInvoke(this, args)}" } else { return "dynamic method ${name}(${args})" } } } MyClass c = new MyClass() println c.method1() println c.method2() println c.method3(12, false) -
Criar Builders simples:
class HtmlBuilder { def out def invokeMethod(String name, args) { if (args) { int idx = 0 if (args[idx] instanceof Map) { Map atts = args[idx] out << "<${name} " atts.entrySet().each{out << "${it.key}='${it.value}'"} out << ">" idx++ } else { out << "<$name>" } if (idx < args.length) { if(args[idx] instanceof Closure) { args[idx].delegate = this args[idx].call() } else { out << args[idx].toString() } } out << "</$name>" } else { out << "<$name />" } } } def html = new HtmlBuilder(out: new StringWriter()) html.html { head { meta(name: 'keywords', content: 'HTML, CSS, XML') } body(onload: 'javascript: return false;') { p 'This is a page' } }
ExpandoMetaClass
Groovy inclui uma MetaClass especial chamada ExpandoMetaClass. Essa MetaClass permite que construtores, métodos e propriedades sejam facilmente adicionados a (ou sobreescritos em – overriding) qualquer java.lang.Class, em tempo de execução.
Por exemplo, para adicionar um método:
String.class.metaClass.reverse << {->
delegate[delegate.length()-1..0]
}
println ('Using ExpandoMetaClass'.reverse())
Ou para sobreescrever um método já existente:
println 'Groovy'.endsWith('y ')
def oldMethod = String.class.metaClass.getMetaMethod('endsWith')
String.class.metaClass.endsWith = {String text ->
oldMethod.doMethodInvoke(delegate, text.trim())
}
println 'Groovy'.endsWith('y ')
Referências adicionais
