How to Use the Intercept-Cache-Invoke Design Pattern in Groovy
At times we might want to bypass the invocation of a method in a Groovy class. For example, a method in an Order class might be used to specify express shipping, but the shipper conceivably could impose a limit based on weight or size, and therefore the method specifying express shipping should be bypassed. Furthermore, the exact weight (or size) might vary depending on fuel costs, availability of vehicles, etc. and consequently the Groovy class would have to be constantly maintained to reflect the latest weight/size restriction. That adds cost to maintaining our business layer! A better approach is to implement the Intercept-Cache-Invoke design pattern by creating an "interceptor", a Java class that will determine if one or more methods of a class should be bypassed. Using an interceptor obviates the need to constantly maintain our business classes.
To learn how to use the Intercept-Cache-Invoke design pattern in Groovy, follow these 3 steps:
- Open your text editor and type in the following Groovy statements:
The
// Define Order class class Order { String orderItems String customer Date orderDate int estimatedWeight public String toString() { "Items: $orderItems estimated weight $estimatedWeight" } public String expressShipping() { "Preferred express shipping vendor for $orderItems" } } // Define Interceptor implementation class OrderInterceptor implements Interceptor { int WEIGHT_LIMIT=100 boolean methodCanBeCalled=true int weight=0 String orderitems="" boolean doInvoke() { methodCanBeCalled } Object beforeInvoke (Object object, String methodName, Object[] args) { // if method is constructor then remember the estimated weight: if (methodName=="ctor") { orderitems=args[0]['orderItems'] weight=args[0]['estimatedWeight'] } if (methodName == 'expressShipping' && weight > WEIGHT_LIMIT) { methodCanBeCalled = false } } Object afterInvoke (Object object, String methodName, Object[] args, Object message) { if (methodName == 'expressShipping' && weight > WEIGHT_LIMIT) { methodCanBeCalled = true message="Order $orderitems too heavy for express shipping" } message } } // Create a proxy object and attach our interceptor implementation def myProxy=ProxyMetaClass.getInstance(Order) def myInterceptor=new OrderInterceptor() myProxy.interceptor=myInterceptor // Apply interceptor myProxy.use { def heavyOrder = new Order(orderItems : "Big Thing", customer : "Acme", orderDate: new Date(), estimatedWeight: 101) // try express shipping (will fail!) println(heavyOrder.expressShipping()) def lightOrder = new Order(orderItems : "Smaller Thing", customer : "Jones Supply", orderDate: new Date(), estimatedWeight: 99) // try express shipping (will succeed!) println(lightOrder.expressShipping()) }
expressShipping
method is the focus of this demonstration as the estimated weight of the order items might require this method, which requests express shipping for an order, to be bypassed. I've supplied comments to describe the overall flow of the script. Notice that we have four pieces to the script: 1) Order class, 2) Interceptor interface implementation, 3) proxy object creation and 4) use of the proxy. The proxy supports aninterceptor
property that is assigned to the interceptor implementation. To kick in the interceptor, we use theuse
method of theProxyMetaClass
class. Now, any call to the Order class methods will automatically trigger the callback methods. ThebeforeInvoke
method will be called before each method of theOrder
class, including the constructor. Note that the method name is passed to each callback method and the constructor name isctor
. When the constructor is intercepted, we have access to all arguments usingargs[0]
. This array element points to a map of the arguments. The order items and estimated weight are stored. The order items will be printed in the message generated by the call back methods. The estimated weight is required to check for compliance. Note that thedoInvoke
method determines if an Order method will be invoked. If the return value isfalse
then the method is bypassed, else the method will be allowed to execute. - Save your file as
UseGroovyInterceptCacheInvoke.groovy
. - In the command prompt, type in the command to interpret and run your script:
The output shows the interceptor correctly determines if the express shipping method should be allowed to execute.