| Slide |
Audio |
Slide Content |
| 1 |
Intro
Welcome to metaprogramming in
groovy. We're going to explain what meta programming is
and walk you through 4 different metaprogramming techniques
available
in the Groovy.
Many Groovy users first see metaprogramming in
the
Grails GORM framework or various Groovy DSLs. This
presentation explains how frameworks like this work and help
you make your own.
Anyone familar with java-like syntax
should have no trouble following along.
|
Metaprogramming in
Groovy
* Quick Explanation
* 4 Techniques
* No Groovy Experience Required
|
| 2 |
Intro to
Metaprogramming
There is a folk tale of a computer science student caught talking in
class. As punishment, the professor made her write a program
that prints out "I will not talk in class" 1000 times, along with the
line number. The catch was that she wasn't allowed to use iteration,
recursion, gotos and any looping.
The solution to this, of course, is to write a
program that
prints out the source code the professor is asking for. |
1. I will not talk in class
2. I will not talk in class
...
999. I will not talk in class
1000. I will not talk in class |
| 3 |
Intro to
Metaprogramming
Writing a program that writes another program is an
essential part of metaprogramming. This comes in many forms.
Simple code generators are one form of metaprogramming.
Generating XML Bean bindings is a common practice from the
Java world.
Monkey patching is another popular form of
metaprogramming.
This is the act of opening classes at runtime to add behavior without
altering the original source code. |
(1..1000).each {
println ("println $it. I will not talk in class.")
} |
4
|
Examples of
Metaprogramming
Many languages support compile time metaprogramming..
The C Preprocessor, Lisp Macros, and C++ Templates are all
examples. These features are used to manipulate
behavior and
syntax of the computer program before it is compiled.
Groovy supports extensive runtime metaprogramming, which is
what our
concern is today. |
#define MIN(x, y) ((x) > (y) ? (y) : (x))
(defmacro Square (X)
'(* ,X ,X))
|
| 5 |
String Evalutation I
Groovy contains the ability to treat data like code and
execute data at runtime.
Both these examples show a string being evaluated at runtime,
returning an Integer.
Where do the strings come from? A file, or user
input, or
maybe Groovy source on disk? |
new GroovyShell().evaluate("2+4")
def input = new Binding([a: 2, b: 4]) new GroovyShell(input).evaluate("a + b")
|
| 6 |
String
Evalutation II
Why would you do this?
It's an alternative to futzing with
classloaders to get a class dynamically loaded, like in
Java.
Writing DSLs is another reason. Exposing code snippets in
config files allows you to create a program at a higher level of
abstraction than the groovy syntax level. Groovy's flexible
syntax means code exposed in a file doesn't even have to look like
groovy |
orderPizza.dsl
--------------
size large
crust thin
toppings Olives, Onions, Bell_Pepper
address "101 Main St.,, ..."
card visa, '1234-1234-1234-1234'
(from Programming Groovy) |
| 7 |
ExpandoMetaClass
I
Moving on... Sometimes the core JDK classes don't contain all the
methods you need. Perhaps your project has a StringUtils class that allows
you to sanitize input Or maybe a DateUtils that can
add days to a date. Wouldn't it be nice if you could
dynamically add a method to, say, Integer or String? |
"Robert'); DROP
TABLE
Students;--".sanitize()
3.days.after.today
|
| 8 |
ExpandoMetaClass
II
ExpandoMetaClass to the rescue. This allows
you to dynamically add methods, constructors, and properties to
existing classes.
Every class in Groovy, including Java classes, has a meta
class. When a
method is invoked on a class, the call is first dispatched to
the metaclass. If the method
doesn't exist there, then the method on the class is invoked.
|
[process diagram showing method dispatch] |
| 9 |
ExpandoMetaClass
III
This is a great way to add convenience methods to existing classes.
This example creates a capitalize method on String by
assigning a
closure to the metaclass. The target of the
method call is available in the closure as the delegate object. In the
hello example, the delegate is the uncapitalized "hello" string.
|
String.metaClass.capitalize
= { if (!delegate) return delegate;
delegate.substring(0, 1).toUpperCase() +
delegate.substring(1).toLowerCase();
}
assert "Hello" == "hello".capitalize() |
| 10 |
ExpandoMetaClass IV
A common usage is in unit testing with mock
objects.
Grails controllers are seen as difficult to mock out
because
so many methods on them are supplied at runtime by the framework
itself. An easy way to mock out Grails controllers is to add log() and
request() methods to the ExpandoMetaClass of the class under test. |
MyController.metaClass.request = {
...
}
MyController.metaClass.log = {
...
} |
| 11 |
invokeMethod I
Sometimes
you don't know what methods will be called on your object until
runtime. Typically, calling a non-existent method results in a
MethodMissing exception.
But many objects in
Groovy respond to method calls that aren't defined in source code.
Building XML dynamically is a prime example.
|
new
XmlBuilder()
.root() {
Groovy()
Users()
of()
Minnesota()
} |
| 12 |
invokeMethod II
Generating
XML based on the previous example can be accomplished by overridding a
single method call: invokeMethod.
invokeMethod is
called on almost every method invocation. You can provide dynamic
methods by intercepting the method name and parameters from within it
|
class XmlBuilder { def invokeMethod(String name, args) { println "<$name>"
if((args) && (args[0] instanceof Closure)) {
args[0].delegate = this
args[0].call()
}else if (args) {
println args[0].toString()
} println "</$name>"
}
} |
| 13 |
invokeMethod III
invokeMethod
can also be used to wrap existing methods before invocation. Aspect
Oriented Around advice for logging is implemented here in 4 lines of
code:
Override invokeMethod, log a message, invoke the
original target, and log a message at completion. No extra libraries or
instrumentation needed!
|
class
Wrapper implements GroovyInterceptable { def foo() { System.out.println "foo invoked"
}
def invokeMethod(String name, args) {
System.out.println ("Beginning $name")
Wrapper.metaClass.getMetaMethod(name, args).invoke(this, args) System.out.println ("Completed $name")
}
} |
| 14 |
InvokeMethod
IV
invokeMethod
is a great aid for writing builders, validating parameters, logging,
data massaging, and DSLs.
A lot of dynamic
flexibility comes with the ability to intercept every method call at
runtime. However, this advantage is also its weakness. A lot of
overhead comes with intercepting every method call at runtime. |
Pros:
invokeMethod called for every invocation
Cons:
invokeMethod called for every invocation |
| 15 |
MethodMissing I
Normally, when a missing method or property is called, Groovy will throw a
MissingProperty or MissingMethod Exception.
But if you override a method called MethodMissing, then Groovy is
going to invoke that method before throwing an exception. Using this,
you can intercept only those method calls that don't exist on your
class.
|
new String().foo
Exception thrown: groovy.lang.MissingPropertyException: No such
property: foo for class: java.lang.String |
| 16 |
MethodMissing II
The methodMissing concept was invented for Smalltalk in
the 80s.
By using it you avoid the overhead of invokeMethod and add runtime functionality lazily.
These examples are simply doing printlines for any method or property
invocation that can't be found.
|
class Catcher { def methodMissing(String name, args) { println "$name called" }
def propertyMissing(String name, args) { println "$name accessed"
}
} |
| 17 |
MethodMissing III
This is how Grail's GORM works.
At design time, your domain object (a book perhaps) will have
the property title defined. But there is no definition of a
findByTitle method. Instead, it is synthesized at runtime
using methodMissing.
The method parses "findByTitle" and calls the database with the data
encoded in your method name.
|
def book = Book.findByTitle("Groovy in Action")
|
| 18 |
MethodMissing IV
The significant overhead of methodMissing led the
Grails team to invent a pattern called "InterceptCacheInvoke".
When methodMissing is called the first time you create a new method to
invoke. Then add that method to the metaclass, so future invocations will dispatch directly to the metaclass. Finally,
you invoke the method you created.
|
class Catcher { def methodMissing(String name, args) { def method = { println "$name called" } Catcher.metaClass."$name" = method
return method(args) }
} |
| 19 |
And that's it... you've seen four different metaprogramming techniques in Groovy:
We've evalutated Strings as code,
We've added methods and properties with ExpanoMetaclass,
We've synthesize methods at runtime with invokeMethod,
and we've optimized dynamic methods using methodMissing and interceptcacheinvoke. |
shell.evaluate("2+4")
String.metaClass.capitalize = { ...
def invokeMethod(String name, args) { ...
def methodMissing(String name, args) { ...
|
| 20 |
This pecha kucha was created by the Groovy
Users of Minnesota, meeting the first first Tuesday of the month in
Minneapolis to discuss and learn about Groovy and Grails. Visit our
website at www.groovy.mn and, if you're in the area, please stop by and
say hi.
Thanks for listening!
|
Groovy Users of Minnesota http://groovy.mn
Mike Calvo Hamlet D'Arcy Robert Fischer Mike Hugo Brian Michelich Jesse O'Neill-Oine Peter Pascale Paul Wiedel Justin Grammens
|