One of the exciting characteristics of Groovy is Meta Object Protocol (MOP). In a nutshell, the term metaprogramming refers to writing code that can dynamically change its behavior at runtime. A Meta Object Protocol refers to the capabilities of a dynamic language that enable metaprogramming. In this recipe, we are going to look at one of the capabilities of the MOP, which is ExpandoMetaClass
.
Groovy's ExpandoMetaClass
lets you assign behavior and state to classes at runtime without editing the original source code; it is essentially a layer above the original class. In the next section of the recipe, we will show you how to achieve such a result.
Adding a new method to a Groovy (or Java) class is straightforward. We are going to perform the following given steps to add a getInEuros
method to the BigDecimal
Java class in order to convert US dollars to euros:
import java.text.NumberFormat BigDecimal.metaClass.getInEuros = { -> def exchangeRate = 0.763461 def nf = NumberFormat.getCurrencyInstance(Locale.US) nf.setCurrency(Currency.getInstance('EUR')) nf.format(delegate * exchangeRate) }
assert 1500.00.inEuros == 'EUR1,145.19'
Each Groovy object has an accompanying instance of a class named ExpandoMetaClass
(reachable through the metaClass
property) that holds a reference to the methods that can be called on an object, including:
find
on collections)metaClass
Every time a method on an object is invoked, a dynamic dispatcher mechanism is used to delegate to the companion ExpandoMetaClass
.
The getInEuros
closure, which we have added to the BigDecimal
class, contains only the US dollar/euro conversion code. The only aspect worth mentioning is the delegate variable, which refers to the object on which we call the method.
It is important to note that a method added to a class using ExpandoMetaClass
will effectively modify the class across all the threads of the application; therefore, it is not a local modification. If you need to modify only a single instance of a class, you can access the metaClass
property on an instance, as in the following code snippet:
class Customer { Long id String name String lastName } def c = new Customer() c.metaClass.fullName { "$name $lastName" } c.name = 'John' c.lastName = 'Ross' assert c.fullName() == 'John Ross'
ExpandoMetaClass
can also be used to add:
// defines a new constructor Customer.metaClass.constructor << { String name -> new Customer(name: name) } def c = new Customer('John') assert 'John' == c.name
In this example, we use the left shift operator to append a new constructor. The <<
operator can be used for chaining constructors or methods, like in this example:
Customer.metaClass.constructor << { String name -> new Customer(name: name) } << { Long id, String fullName -> new Customer( id: id, name: fullName.split(',')[0], lastName: fullName.split(',')[1] ) } def c0 = new Customer('Mike') c0.name = 'Mike' def c1 = new Customer(1000, 'Mike,Whitall') assert c1.name == 'Mike' assert c1.lastName == 'Whitall'
The Customer
class has been enhanced with two concatenated constructors: the first accepting a string and the second accepting a Long
and a string. The second constructor uses the split
function to assign the name and last name of the customer.
Customer.metaClass.'static'.sayHello = { -> "hello! I'm your customer" } assert "hello! I'm your customer" == Customer.sayHello()
Customer.metaClass.gsm = null def c = new Customer() c.gsm = '123456' assert '123456' == c.gsm
3.133.116.137