METHODSYNTHESISUSINGEXPANDOMETACLASS 21914.5 Method Synthesis Using ExpandoMetaClass In Section 14.4, Method Synthesis Using methodMissing, on page214, you saw how to synthesize methods.
Trang 1METHODSYNTHESISUSING METHODMISSING 218
System.out println "methodMissing called for $name"
def methodInList = plays.find { it == name.split( 'play' )[1]}
if (methodInList)
{
def impl = { Object[] vargs ->
return "playing ${name.split('play')[1]} "
The output from the previous code is as follows:
intercepting call for work
working
intercepting call for playTennis
methodMissing called for playTennis
playing Tennis
intercepting call for playTennis
playing Tennis
Trang 2METHODSYNTHESISUSINGEXPANDOMETACLASS 219
14.5 Method Synthesis Using ExpandoMetaClass
In Section 14.4, Method Synthesis Using methodMissing, on page214,
you saw how to synthesize methods If you don’t have the privilege to
edit the class source file or if the class is not a POGO, that approach
will not work You can synthesize methods using theExpandoMetaClass
in these cases
You already saw how to interact with MetaClass in Section 13.2,
Inter-cepting Methods Using MetaClass, on page 197 Instead of providing
an interceptor for a domain method, you implement themethodMissing( )
method on it Let’s take thePersonclass (and the boringjack) from
Sec-tion 14.4, Method Synthesis Using methodMissing, on page 214, but
instead we’ll useExpandoMetaClass, as shown here:
Person.metaClass.methodMissing = { String name, args ->
def plays = [ 'Tennis' , 'VolleyBall' , 'BasketBall' ]
System.out println "methodMissing called for $name"
def methodInList = plays.find { it == name.split( 'play' )[1]}
if (methodInList)
{
def impl = { Object[] vargs ->
return "playing ${name.split('play')[1]} "
Trang 3METHODSYNTHESISUSINGEXPANDOMETACLASS 220
No signature of method: Person.playPolitics()
is applicable for argument types: () values: {}
When you called work( ) on jack, Person’s work( ) was executed directly
If you call a nonexistent method, however, it is routed to the Person’s
MetaClass’smethodMissing( ).4You implement logic in this method similar
to the solution in Section14.4, Method Synthesis Using methodMissing,
on page 214 Repeated calls to supported nonexistent method do not
incur overhead, as you can see in the previous output for the second
call toplayTennis( ) You cached the implementation on the first call
In Section 13.2, Intercepting Methods Using MetaClass, on page 197,
you intercepted calls usingExpandoMetaClass’sinvokeMethod( ) You can
mix that withmethodMissing( ) to intercept calls to both existing methods
and synthesized methods, as shown here:
Person.metaClass.invokeMethod = { String name, args ->
System.out println "intercepting call for ${name}"
def method = Person.metaClass.getMetaMethod(name, args)
4 methodMissing ( ) of the MetaClass will take precedence over methodMissing ( ) if present in
your class Methods of your class’s MetaClass override the methods in your class.
Trang 4METHODSYNTHESISUSINGEXPANDOMETACLASS 221
Person.metaClass.methodMissing = { String name, args ->
def plays = [ 'Tennis' , 'VolleyBall' , 'BasketBall' ]
System.out println "methodMissing called for ${name}"
def methodInList = plays.find { it == name.split( 'play' )[1]}
if (methodInList)
{
def impl = { Object[] vargs ->
return "playing ${name.split('play')[1]} "
The output from the previous code is as follows:
intercepting call for work
working
intercepting call for playTennis
methodMissing called for playTennis
playing Tennis
intercepting call for playTennis
playing Tennis
Trang 5SYNTHESIZINGMETHODS FOR SPECIFICINSTANCES 222
invokeMethod vs methodMissing
invokeMethod( ) is a method of GroovyObject methodMissing( )
was introduced later in Groovy and is part of the
MetaClass-based method handling If your objective is to handle calls to
nonexisting methods, implementmethodMissing( ) because this
involves low overhead If your objective is to intercept calls to
both existing and nonexisting methods, useinvokeMethod( )
14.6 Synthesizing Methods for Specific Instances
I showed how you can inject methods into specific instances of a class
in Section 14.3, Injecting Methods into Specific Instances, on page212
You can synthesize methods dynamically as well as into specific
in-stances by providing the instance(s) with a specialized MetaClass Here
is an example:
Download InjectionAndSynthesisWithMOP/SynthesizeInstance.groovy
class Person {}
def emc = new ExpandoMetaClass(Person)
emc.methodMissing = { String name, args ->
"I'm Jack of all trades I can $name"
}
emc.initialize()
def jack = new Person()
def paul = new Person()
Trang 6SYNTHESIZINGMETHODS FOR SPECIFICINSTANCES 223
The previous code reports the following:
I'm Jack of all trades I can sing
I'm Jack of all trades I can dance
I'm Jack of all trades I can juggle
groovy.lang.MissingMethodException:
No signature of method: Person.sing()
is applicable for argument types: () values: {}
Like injecting into specific instances, synthesizing methods for specific
instances is limited to Groovy objects
In this chapter, you learned how to intercept, inject, and synthesize
methods Groovy MOP makes it easy to perform AOP-like activities
You can create code that is highly dynamic, and you can create highly
reusable code with fewer lines of code You’ll put all these skills together
in the next chapter
Trang 7Chapter 15
MOPping Up
You’ve seen how to synthesize methods, and in this chapter, you’ll seehow to synthesize an entire class Rather than creating explicit classesahead of time, you can create classes on the fly, which gives you moreflexibility Delegation is better than inheritance, yet it has been hard toimplement in Java You’ll see how Groovy MOP allows method delega-tion with only one line of code I’ll wrap this chapter up by reviewing thedifferent MOP techniques you’ve seen in the previous three chapters
15.1 Creating Dynamic Classes with Expando
In Groovy you can create a class entirely at runtime Suppose you’rebuilding an application that will configure devices You don’t have aclue what these devices are—you know only that devices have proper-ties and configuration scripts You don’t have the luxury of creating anexplicit class for each device at coding time So, you’ll want to synthe-size classes at runtime to interact with and configure these devices InGroovy, classes can come to life at runtime at your command
The Groovy class that gives you the ability to synthesize classes ically isExpando, which got its name because it is dynamically expand-able You can assign properties and methods to it either at construc-tion time using a Map or at any time dynamically Let’s start with anexample to synthesize a classCar I’ll show two ways to create it usingExpando
Trang 8dynam-CREATINGDYNAMICCLASSES WITHEXPANDO 225
Download MOPpingUp/UsingExpando.groovy
carA = new Expando()
carB = new Expando(year: 2007, miles: 0)
carA.year = 2007
carA.miles = 10
println "carA: " + carA
println "carB: " + carB
The output from the previous code is as follows:
carA: {year=2007, miles=10}
carB: {year=2007, miles=0}
You createdcarA, the first instance ofExpando, without any properties
or methods You injected the year and miles later On the other hand,
you created carB, the second instance of Expando, with the year and
milesinitialized at construction time
You’re not restricted to properties You can define methods as well and
invoke them like you would invoke any method Let’s give that a try
Once again, you can define a method at construction time or inject
You can easily work with Car objects without explicitly creating a Car
class, as in the following code You’re parsing the content of the file, first
Trang 9CREATINGDYNAMICCLASSES WITHEXPANDO 226
extracting the property names Then you create instances of Expando,
one for each line of data in the input file, and populate it with values
for the properties You even add a method, in the form of a closure, to
compute the average miles driven per year until 2008 Once the objects
are created, you can access the properties and call methods on them
dynamically You can also address the methods/properties by name, as
shown in the end
car = new Expando()
it.split( ", " ).eachWithIndex { value, index ->
props.each { name -> print "$name " }
println " Avg MPY"
ampyMethod = 'ampy'
cars.each { car ->
for (String property : props) { print "${car[property]} " }
println car "$ampyMethod" ()
}
// You may also access the properties/methods by name
car = cars[0]
println "$car.miles $car.year $car.make ${car.ampy()}"
The output from the previous code is as follows:
miles year make Avg MPY
42451 2003 Acura 8490.2
24031 2003 Chevy 4806.2
14233 2006 Honda 7116.5
42451 2003 Acura 8490.2
Trang 10METHODDELEGATION: PUTTINGITALLTOGETHER 227
Use Expando whenever you want to synthesize classes on the fly It is
lightweight and flexible One place where you will see them shine is to
create mock objects for unit testing (see Section 16.8, Mocking Using
Expando, on page251)
15.2 Method Delegation: Putting It All Together
You use inheritance to extend the behavior of a class On the other
hand, you use delegation to rely upon contained or aggregated objects
to provide the behavior of a class Choose inheritance if your intent is to
use an object in place of another object Choose delegation if the intent
is to simply use an object Reserve inheritance for an is-a or kind-of
relationship only; you should prefer delegation over inheritance most
of the time However, it’s easy to program inheritance, because it takes
only one keyword,extends But it’s hard to program delegation, because
you have to write all those methods that route the call to the contained
objects Groovy helps you do the right thing By using MOP, you can
easily implement delegation with a single line of code, as you’ll see in
this section
In the following example, a Manager wants to delegate work to either
a Worker or an Expert You’re using methodMissing( ) and
ExpandoMeta-Classto realize this If a method called on the instance ofManagerdoes
not exist, itsmethodMissing( ) routes it to either theWorkeror theExpert,
whicheverrespondsTo( ) to the method (see Section12.2, Querying
Meth-ods and Properties, on page 190) If there are no takers for a method
among the delegates and the Manager does not handle it, the method
def simpleWork1(spec) { println "worker does work1 with spec $spec" }
def simpleWork2() { println "worker does work2" }
}
class Expert
{
def advancedWork1(spec) { println "Expert does work1 with spec $spec" }
def advancedWork2(scope, spec)
{
println "Expert does work2 with scope $scope spec $spec"
}
}
Trang 11METHODDELEGATION: PUTTINGITALLTOGETHER 228
class Manager
{
def worker = new Worker()
def expert = new Expert()
def schedule() { println "Scheduling " }
def methodMissing(String name, args)
{
println "intercepting call to $name "
def delegateTo = null
if (name.startsWith( 'simple' )) { delegateTo = worker }
if (name.startsWith( 'advanced' )) { delegateTo = expert }
if (delegateTo?.metaClass.respondsTo(delegateTo, name, args))
{
Manager.metaClass "${name}" = { Object[] varArgs ->
return delegateTo.invokeMethod(name, *varArgs)
peter.advancedWork2( 'protype' , 'fast' )
peter.advancedWork2( 'product' , 'quality' )
intercepting call to simpleWork1
worker does work1 with spec fast
worker does work1 with spec quality
Trang 12METHODDELEGATION: PUTTINGITALLTOGETHER 229
intercepting call to simpleWork2
worker does work2
worker does work2
intercepting call to advancedWork1
Expert does work1 with spec fast
Expert does work1 with spec quality
intercepting call to advancedWork2
Expert does work2 with scope protype spec fast
Expert does work2 with scope product spec quality
intercepting call to simpleWork3
groovy.lang.MissingMethodException:
No signature of method: Manager.simpleWork3()
is applicable for argument types: () values: {}
You figured out a way to delegate calls, but that’s a lot of work You
don’t want to put in so much effort each time you want to delegate You
can refactor this code for reuse Let’s first look at how the refactored
code will look like when used in theManagerclass:
Download MOPpingUp/DelegationRefactored.groovy
class Manager
{
{ delegateCallsTo Worker, Expert, GregorianCalendar }
def schedule() { println "Scheduling " }
}
That is short and sweet In the initializer block you call a
yet-to-be-implemented method named delegateCallsTo( ) and send the names of
classes to which you want to delegate unimplemented methods If you
want to use delegation in another class, all it takes now is that code in
the initialization block Let’s take a look at the fancy delegateCallsTo( )
method:
Download MOPpingUp/DelegationRefactored.groovy
ExpandoMetaClass.enableGlobally()
Object.metaClass.delegateCallsTo = {Class klassOfDelegates ->
def objectOfDelegates = klassOfDelegates.collect { it.newInstance() }
delegate.metaClass.methodMissing = { String name, args ->
println "intercepting call to $name "
def delegateTo = objectOfDelegates.find {
it.metaClass.respondsTo(it, name, args) }
Trang 13METHODDELEGATION: PUTTINGITALLTOGETHER 230
if (delegateTo)
{
delegate.metaClass "${name}" = { Object[] varArgs ->
def params = varArgs?: null
return delegateTo.invokeMethod(name, *params)
When you call delegateCallsTo( ) from within your class’s instance
ini-tializer, it adds a methodMissing( ) to the class, which is known within
this closure asdelegate It takes theClasslist provided as an argument
to delegateCallsTo( ) and creates a list of delegates, which are the
can-didates to implement delegated methods In methodMissing( ), the call
is routed to an object among the delegates that will respond to the
method If there are no takers, the call fails The list of classes given to
delegateCallsTo( ) also represents the order of precedence, and the first
one has the highest precedence Of course, you have to see all this in
action, so here is the code to exercise the previous example:
peter.advancedWork2( 'protype' , 'fast' )
peter.advancedWork2( 'product' , 'quality' )
println "Is 2008 a leap year? " + peter.isLeapYear(2008)
Trang 14REVIEW OFMOP TECHNIQUES 231
The previous code produces the following output:
Scheduling
intercepting call to simpleWork1
worker does work1 with spec fast
worker does work1 with spec quality
intercepting call to simpleWork2
worker does work2
worker does work2
intercepting call to advancedWork1
Expert does work1 with spec fast
Expert does work1 with spec quality
intercepting call to advancedWork2
Expert does work2 with scope protype spec fast
Expert does work2 with scope product spec quality
intercepting call to isLeapYear
Is 2008 a leap year? true
intercepting call to simpleWork3
groovy.lang.MissingMethodException:
No signature of method: Manager.simpleWork3()
is applicable for argument types: () values: {}
You can build on this idea further to meet your needs For instance,
if you want to mix some precreated objects, you can send them as an
array to the first parameter ofdelegateCallsTo( ) and have those objects
used along with those created from the delegates classes The previous
example shows how you can use Groovy’s MOP to implement dynamic
behavior such as method delegation
15.3 Review of MOP Techniques
You’ve seen a number of options to intercept, inject, and synthesize
methods In this section, you’ll figure out which option is right for you
Options for Method Interception
I discussed method interception in Chapter 13, Intercepting Methods
Using MOP, on page 194and in Section 14.1, Injecting Methods Using
Categories, on page203 You can useGroovyInterceptable,
ExpandoMeta-Class, or categories
If you have the privilege to modify the class source, you can implement
GroovyInterceptableon the class you want to intercept method calls The
effort is as simple as implementinginvokeMethod( )
If you can’t modify the class or if the class is a Java class, then you can
use ExpandoMetaClass or categories ExpandoMetaClass clearly stands
Trang 15REVIEW OFMOP TECHNIQUES 232
out in this case because a singleinvokeMethod( ) can take care of
inter-cepting any methods of your class Categories, on the other hand,
would require separate methods, one per intercepted method Also, if
you use categories, you’re restricted by theuse( ) block
Options for Method Injection
I discussed method injection in Section 14.1, Injecting Methods Using
Categories, on page203 You can use categories orExpandoMetaClass
Categories compete well withExpandoMetaClassesfor method injection
If you use categories, you can control the location where methods are
injected You can easily implement different versions of method
injec-tion by using different categories You can easily nest and mix multiple
categories as well The control offered by categories—that method
injec-tion takes effect only within theuse( ) blocks and is limited to the
exe-cuting thread—may also be considered as a restriction If you want to
use the injected methods at any location and also want to inject static
method and constructors,ExpandoMetaClassis a better choice Beware,
though, thatExpandoMetaClassis not the default MetaClassin Groovy
Using the ExpandoMetaClass, you can inject methods into specific
in-stances of a class instead of affecting the entire class This is available
only for POGOs, however
Options for Method Synthesis
I discussed method injection in Section 14.4, Method Synthesis Using
methodMissing, on page214 You can usemethodMissing( ) on a Groovy
object orExpandoMetaClass
If you have the privilege to modify the class source, you can implement
the methodMissing( ) method on the class for which you want to
synthe-size methods You can improve performance by injecting the method on
the first call If you need to intercept your methods at the same time,
you can implementGroovyInterceptable
If you can’t modify the class or if the class is a Java class, then you
can add the method methodMissing( ) to the class’s ExpandoMetaClass
If you want to intercept method calls at the same time, implement
invokeMethod( ) on theExpandoMetaClassas well
Using the ExpandoMetaClass, you can synthesize methods into specific
instances of a class instead of affecting the entire class This is available
only for POGOs, however