Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

We can use the ProxyMetaClass to replace the metaclass being used by intercept methods in a class within a selected block for the current thread.

Interceptors with ProxyMetaClass

By using ProxyMetaClass, we can attach an interceptor to a class for a block of code. The Groovy-supplied Interceptor interface has three methods. The beforeInvoke() method specifies code to be executed before the intercepted method, the doInvoke() indicates whether to execute the intercepted method, and afterInvoke() executes after the intercepted method finishes, or after a false-returning doInvoke(). The result parameter passed to afterInvoke() is the result of executing the method, or what was returned from beforeInvoke() if the intercepted method wasn't executed. What afterInvoke() returns is returned from the method call in the main flow of the program.

Code Block
class MyClass{
  public MyClass(String s){ println "constructing $s" }
  public String sayHello(String name){
    println "saying hello to $name"
    "Hello " + name //return this value
  }
}

class MyInterceptor implements Interceptor{
  Object beforeInvoke(Object object, String methodName, Object[] arguments){
    println "  BEFORE $object .$methodName $arguments"
    if( methodName == 'sayHello' ) arguments[0] += ' and family'
                                                 //we can change the arguments
    null //value returned here isn't actually used anywhere else
  }
  boolean doInvoke(){ true } //whether or not to invoke the intercepted
                             //method with beforeInvoke's copy of arguments

  Object afterInvoke(Object object, String methodName, Object[] arguments,
                     Object result){
    println "  AFTER $object .$methodName $arguments: $result"
    if( methodName == 'sayHello' ) result= (result as String) + ' and in-laws'
                                             //we can change the returned value
    result
  }
}

def proxy= ProxyMetaClass.getInstance( MyClass )
                                          //create proxy metaclass for MyClass
proxy.interceptor= new MyInterceptor()
                         //attach new interceptor to MyClass's proxy metaclass
proxy.use{
  def invoice= new MyClass('trade')
  println invoice.sayHello('Ms Pearl')
}

/*example output:
  BEFORE class MyClass .ctor {"trade"}
constructing trade
  AFTER class MyClass .ctor {"trade"}: MyClass@1d63e39
  BEFORE MyClass@1d63e39 .sayHello {"Ms Pearl"}
saying hello to Ms Pearl and family
  AFTER MyClass@1d63e39 .sayHello {"Ms Pearl and family"}: Hello Ms Pearl and family
Hello Ms Pearl and family and in-laws
*/

...

Code Block
class MyClass{
  public String sayHello(String name){
    println "saying hello to $name"
    return "Hello " + name
  }
  public String sayGoodbye(String name){
    println "saying goodbye to $name"
    return "Goodbye " + name
  }
}

class MyInterceptor implements Interceptor{
  def proxy //good idea for interceptor to reference proxytoInvoke= true
        def toInvoke= true     //so we can change whether or not to invoke the original method
  def resultFromSayGoodBye

  Object beforeInvoke(Object object, String methodName, Object[] arguments){
    if( object instanceof MyClass && methodName == 'sayHello' ){
      resultFromSayGoodBye= object.sayGoodbye(arguments[0])
//so we can invoke a different method        //resultFromSayGoodBye= InvokerHelper.invokeMethod( object, 'sayGoodbye', arguments[0] )                      //alternativeso syntaxwe tocan invoke methoda whendifferent uncommentedmethod
       toInvoke= false //don't invoke sayHello
    }
  }
  boolean doInvoke(){ toInvoke }

  Object afterInvoke(Object object, String methodName, Object[] arguments,
                     Object result){
    if( object instanceof MyClass && methodName == 'sayHello' ){
      toInvoke= true
      result= resultFromSayGoodBye
    }
    result
  }
}

//a utility to match up class, interceptor, and code...
def useInterceptor= { Class theClass, Class theInterceptor, Closure theCode->
  def proxy= ProxyMetaClass.getInstance( theClass )
  def interceptor= theInterceptor.newInstance()
                //must use dynamic constructor here because class proxy.interceptor= interceptor
not yet known
  proxy.interceptor.proxy= proxyinterceptor
  proxy.use( theCode )
}

useInterceptor( MyClass, MyInterceptor ){
  println new MyClass().sayHello('Ms Pearl')
}

/*output:
saying goodbye to Ms Pearl
Goodbye Ms Pearl
*/

...

Code Block
class MyInterceptor implements Interceptor{
  def proxy
  Object beforeInvoke(Object object, String methodName, Object[] arguments){
    null
  }
  boolean doInvoke(){ true }
  Object afterInvoke(Object object, String methodName, Object[] arguments,
                     Object result){
    if( object instanceof ArrayList && methodName == 'size' ){
      result = (result as Integer) + 10 //add 10 to size of ArrayLists
    }
    result
  }
}

def useInterceptor= { Class theClass, Class theInterceptor, Closure theCode->
  def proxy= ProxyMetaClass.getInstance( theClass )
  def interceptor= theInterceptor.newInstance()
  proxy.interceptor= interceptor
  proxy.use( theCode )
}

useInterceptor( ArrayList, MyInterceptor ){
  assert ['a', 'b', 'c'].size() == 13
}

We can prevent methods being intercepted inside the interceptor by using special & notation:

Code Block

class MyInterceptor implements Interceptor{
  Object beforeInvoke( Object object, String methodName, Object[] arguments ){
    null
  }
  boolean doInvoke(){ true }

  Object afterInvoke( Object object, String methodName,Object[] arguments,
                      Object result ){
    if( object instanceof ArrayList && methodName == 'size' ){
      result = (result as Integer) + [1,2,3,4,5,6,7,8,9,10].&size()
                 // & before method name prevents re-interception of method
    }
    result
  }
}

def useInterceptor= { Class theClass, Class theInterceptor, Closure theCode->
  def proxy= ProxyMetaClass.getInstance( theClass )
  def interceptor= theInterceptor.newInstance()
  proxy.interceptor= interceptor
  proxy.use( thistheCode utility)
from}
theuseInterceptor( previousArrayList, exampleMyInterceptor ){
  assert ['a', 'b', 'c'].size() == 13
}

The interception is Like categories, interceptors are only valid for a certain block in the current thread. We can also combine categories with interceptors in various ways, also only valid in the current thread:

Code Block
class MyCategory{
  static String exaggeratecategorize( String s ){ "exaggeratedcategorized: $s" }
}

class StringInterceptor implements Interceptor{
  static proxy
  Object beforeInvoke(Object object, String methodName, Object[] arguments){
    proxy.interceptor= null //prevents interception inside interceptor
    if( object instanceof String )
      use(MyCategory){
        assert object.exaggerate&categorize() == "exaggeratedcategorized: $object"
}     proxy.interceptor= this}
    null
  }
  boolean doInvoke(){ true }
  Object afterInvoke(Object object, String methodName, Object[] arguments,
                     Object result){
    if( object instanceof String )
      result= "intercepted: $result"
    result
  }
}

def useInterceptor= { Class theClass, Class theInterceptor, Closure theCode->
  def proxy= ProxyMetaClass.getInstance( theClass )
  def interceptor= theInterceptor.newInstance()
  proxy.interceptor= interceptor
  proxy.use( theCode )
}

useInterceptor( String, StringInterceptor ){
  assert new String('silver').toString() == 'intercepted: silver'

  use(MyCategory){
    assert new String('golden').exaggeratecategorize() ==
                                     'intercepted: exaggeratedcategorized: golden'
  }

  Thread.start{ //no interception in spawned thread...
    use(MyCategory){
      assert new String('bronze').exaggeratecategorize() == 'exaggeratedcategorized: bronze'
    }
  }
}

Unintercepted Interceptors

Usually we don't want to intercept methods inside the interceptor. We need to define our own UninterceptedInterceptor for thisThe special & notation for bypassing interceptors handles simple code, but for more complex code we often need our own UninterceptedInterceptor:

Code Block
abstract class UninterceptedInterceptor implements Interceptor{
  def proxy= null //we need to know the proxy...

  abstract Object doBefore( Object object, String methodName,
                            Object[] arguments )

  public Object beforeInvoke( Object object, String methodName,
                              Object[] arguments ){

    proxy.interceptor= null //...so we can turn off interception...
    def result
    try{
      result= doBefore(object, methodName, arguments) 
    }catch(Exception e){
      throw e
    }finally{
      proxy.interceptor= this //...and turn interception back on
    }
    result
  }
  abstract boolean doInvoke()

  abstract Object doAfter( Object object, String methodName, Object[] arguments,
                           Object result )

  public Object afterInvoke( Object object, String methodName,
                             Object[] arguments, Object result ){
    proxy.interceptor= null //turn off interception
    try{
      result= doAfter(object, methodName, arguments, result)
    }catch(Exception e){
      throw e
    }finally{
      proxy.interceptor= this //turn interception back on
    }
    result
  }
}

With the UninterceptedInterceptor class and useInterceptor utility, we can demonstrate a certain method being intercepted outside of the interceptor only:

Code Block
class MyInterceptor extends UninterceptedInterceptor{
  Object doBefore( Object object, String methodName, Object[] arguments ){
    null
  }
  boolean doInvoke(){ true }

  Object doAfter( Object object, String methodName,Object[] arguments,
                  Object result ){
    if( object instanceof ArrayList && methodName == 'size' ){
      result = (result as Integer) + [1,2,3,4,5,6,7,8,9,10].size()
                //call ArrayList size() method here without stack overflow
    }
    result
  }
}

def useInterceptor= { Class theClass, Class theInterceptor, Closure theCode->
  def proxy= ProxyMetaClass.getInstance( theClass )
  def interceptor= theInterceptor.newInstance()
  proxy.interceptor= interceptor
  interceptor.proxy= proxy
        //we must now store a proxy reference in the interceptor
  proxy.use( theCode )
}

useInterceptor( ArrayList, MyInterceptor ){
  assert ['a', 'b', 'c'].size() == 13
}

Intercepting many classes in one block

Often, we want to intercept more than one class in one block. This example is of an aliasing interceptor, which disables some English-language names for selected classes, and replaces them with Spanish-language names. We re-use the UninterceptedInterceptor class and useInterceptor utility from previous examples.

...

Code Block
class Extras{
  static closureInject(List self, Closure base){
    def z= []
    self.eachWithIndex{ it, i-> z<< {-> it( z[i+1] )} }
    z<< base
    z[0]()
  }
}

use(Extras){
  [ {c-> useInterceptor(ArrayList, ArrayListAliasInterceptor){ c() }},
    {c-> useInterceptor(HashMap, HashMapAliasInterceptor){ c() }},
    {c-> useInterceptor(LinkedHashMap, LinkedHashMapAliasInterceptor){ c() }},

  ].closureInject{
    def a= [1, 3, 5, 7, 9], b= [a:1, c:3, e:5, g:7], c= new LinkedHashMap( [e:5, g:7, i:9] )

    println 'size: '+ a.tamano()
    try{ println a.size(); assert 0 }catch(e){ assert e instanceof MissingMethodException }
    a.todos{ println 'item: '+ it }
    println ''

    println 'size: '+ b.tamano()
    try{ println b.size(); assert 0 }catch(e){ assert e instanceof MissingMethodException }
    b.todos{ println 'item: '+ it }
    println ''

    println 'size: '+ c.tamano()
    try{ println c.size(); assert 0 }catch(e){ assert e instanceof MissingMethodException }
    c.todos{ println 'item: '+ it }
  }
}

Our own ProxyMetaClass

We can define our own proxy meta-classes. One case for which we'd do so is to implement our own style of interceptors, here, an around-interceptor:

...

Code Block
class MyInterceptor implements AroundInterceptor{
  Object aroundInvoke(Object object, String methodName, Object[] arguments, Closure proceed){
    println "  BEFORE $object .$methodName $arguments"
    def result= proceed()
    println "  AFTER $object .$methodName $arguments: $result"
    result
  }
}

class MyClass{
  void sayHi(){ System.out.println 'hi' }
}

def interceptor= new MyInterceptor()
def proxy= MyProxyMetaClass.getInstance( MyClass )
proxy.use{
  proxy.interceptor= interceptor
  new MyClass().sayHi()
}

/*outputs:
  BEFORE class MyClass .ctor {}
  AFTER class MyClass .ctor {}: MyClass@1f5d386
  BEFORE MyClass@1f5d386 .sayHi {}
hi
  AFTER MyClass@1f5d386 .sayHi {}: null
*/

Using many Interceptors with our own ProxyMetaClass

We can only use one interceptor with the ProxyMetaClass supplied by Groovy, so we need to provide our own when attaching more than one interceptor to a class:

Code Block
import org.codehaus.groovy.runtime.InvokerHelper

public class MultiInterceptorProxyMetaClass extends MetaClassImpl{
  protected adaptee= null
  def interceptors= [] //reference a list of interceptors, instead of just one

  MultiInterceptorProxyMetaClass( MetaClassRegistry registry, Class theClass, MetaClass adaptee ){
    super(registry, theClass)
    this.adaptee = adaptee
    if( null == adaptee )
        throw new IllegalArgumentException( "adaptee must not be null" )
  }
  static getInstance(Class theClass){
    def metaRegistry= InvokerHelper.getInstance().getMetaRegistry()
    new MultiInterceptorProxyMetaClass( metaRegistry, theClass, metaRegistry.getMetaClass(theClass) )
  }
  void use(Closure closure){
    registry.setMetaClass(theClass, this)
    registry.getMetaClass(theClass).initialize()
    try{ closure.call() }
    finally{ registry.setMetaClass(theClass, adaptee) }
  }
  void use(GroovyObject object, Closure closure){
    object.setMetaClass(this)
    try{ closure.call() }
    finally{ object.setMetaClass(adaptee) }
  }
  Object invokeMethod( final Object object, final String methodName, final Object[] arguments ){
    doCall(object, methodName, arguments,{ adaptee.invokeMethod(object, methodName, arguments) } )
  }
  Object invokeStaticMethod( final Object object, final String methodName, final Object[] arguments ){
    doCall(object, methodName, arguments, { adaptee.invokeStaticMethod(object, methodName, arguments) } )
  }
  Object invokeConstructor(final Object[] arguments){
    doCall(theClass, "ctor", arguments, { adaptee.invokeConstructor(arguments) } )
  }
  public Object invokeConstructorAt(final Class at, final Object[] arguments){
    doCall(theClass, "ctor", arguments, { adaptee.invokeConstructorAt(at, arguments) } )
  }
  private Object doCall( Object object, String methodName, Object[] arguments, Closure howToInvoke ){
    if( interceptors == [] ){ return howToInvoke.call() }
    def result
    interceptors.each{ //different logic to cater for all the interceptors

      result= it.beforeInvoke(object, methodName, arguments)
      if( it.doInvoke() ){ result= howToInvoke.call() }
      it.afterInvoke(object, methodName, arguments, result)
    }
    result
  }
}

Using a MultiInterceptorProxyMetaClass for the Observer pattern

A common design pattern is the Observer pattern. Using interceptors, we can abstract the observation code into its own class, the ObserverProtocol, which can be used by subclasses. It enables us to add and remove observing objects for an observed object. We use method interception to decouple the observing and observed objects from the observation relationship itself.

...

Code Block
import java.awt.Color

def colorObserver= new ColorObserver()
def coordinateObserver= new CoordinateObserver()
def screenObserver= new ScreenObserver()

def pointProxy= MultiInterceptorProxyMetaClass.getInstance( Point )
pointProxy.interceptors << colorObserver << coordinateObserver //multi-interception used here
pointProxy.use{

  def screenProxy= MultiInterceptorProxyMetaClass.getInstance( Screen )
  screenProxy.interceptors << screenObserver
  screenProxy.use{

    println("Creating Screen s1,s2,s3,s4,s5 and Point p")
    def s1= new Screen('s1'),
        s2= new Screen('s2'),
        s3= new Screen('s3'),
        s4= new Screen('s4'),
        s5= new Screen('s5')
    def p= new Point(5, 5, Color.blue)

    println("Creating observing relationships:")
    println(" - s1 and s2 observe color changes to p")
    println(" - s3 and s4 observe coordinate changes to p")
    println(" - s5 observes s2's and s4's display() method")

    colorObserver.addObserver(p, s1)
    colorObserver.addObserver(p, s2)
    coordinateObserver.addObserver(p, s3)
    coordinateObserver.addObserver(p, s4)
    screenObserver.addObserver(s2, s5)
    screenObserver.addObserver(s4, s5)

    println("Changing p's color:")
    p.setColor(Color.red)

    println("Changing p's x-coordinate:")
    p.setX(4)

    println("done.")
  }
}

/*output:
Creating Screen s1,s2,s3,s4,s5 and Point p
Creating observing relationships:
- s1 and s2 observe color changes to p
- s3 and s4 observe coordinate changes to p
- s5 observes s2's and s4's display() method
Changing p's color:
s1: Screen updated (point subject changed color).
s2: Screen updated (point subject changed color).
s5: Screen updated (screen subject changed message).
Changing p's x-coordinate:
s3: Screen updated (point subject changed coordinates).
s4: Screen updated (point subject changed coordinates).
s5: Screen updated (screen subject changed message).
done.
*/

Using a MultiInterceptorProxyMetaClass and UninterceptableFriendlyInterceptor for the Decorator pattern

We can use more than one uninterceptable interceptor with a proxy meta-class. A good example where this is necessary is the Decorator pattern. We re-use the MultiInterceptorProxyMetaClass from previous examples, but must write a special unintercepted interceptor, which we call an UninterceptableFriendlyInterceptor, that can be used as one of many with the MultiInterceptorProxyMetaClass.

...