Skip to content
Skip to breadcrumbs
Skip to header menu
Skip to action menu
Skip to quick search
Quick Search
Browse
Pages
Blog
Labels
Attachments
Mail
Advanced
What’s New
Space Directory
Feed Builder
Keyboard Shortcuts
Confluence Gadgets
Log In
Dashboard
Groovy
Copy Page
You are not logged in. Any changes you make will be marked as
anonymous
. You may want to
Log In
if you already have an account. You can also
Sign Up
for a new account.
This page is being edited by
.
Paragraph
Paragraph
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Preformatted
Quote
Bold
Italic
Underline
More colours
Strikethrough
Subscript
Superscript
Monospace
Clear Formatting
Bullet list
Numbered list
Outdent
Indent
Align left
Align center
Align right
Link
Table
Insert
Insert Content
Image
Link
Attachment
Symbol
Emoticon
Wiki Markup
Horizontal rule
tinymce.confluence.insert_menu.macro_desc
Info
JIRA Issue
Status
Gallery
Tasklist
Table of Contents
Other Macros
Page Layout
No Layout
Two column (simple)
Two column (simple, left sidebar)
Two column (simple, right sidebar)
Three column (simple)
Two column
Two column (left sidebar)
Two column (right sidebar)
Three column
Three column (left and right sidebars)
Undo
Redo
Find/Replace
Keyboard Shortcuts Help
<p>We can use the ProxyMetaClass to intercept methods in a class within a selected block for the current thread.</p> <h3>Interceptors with ProxyMetaClass</h3> <p>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.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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 */ </pre></td></tr></table> <p>We can invoke a different method instead of the one called:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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 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 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 not yet known proxy.interceptor= interceptor proxy.use( theCode ) } useInterceptor( MyClass, MyInterceptor ){ println new MyClass().sayHello('Ms Pearl') } /*output: saying goodbye to Ms Pearl Goodbye Ms Pearl */ </pre></td></tr></table> <p>We can even use interceptors on predefined Java classes:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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) + 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 } </pre></td></tr></table> <p>We can prevent methods being intercepted inside the interceptor by using special & notation:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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( theCode ) } useInterceptor( ArrayList, MyInterceptor ){ assert ['a', 'b', 'c'].size() == 13 } </pre></td></tr></table> <p>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:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class MyCategory{ static String categorize( String s ){ "categorized: $s" } } class StringInterceptor implements Interceptor{ Object beforeInvoke(Object object, String methodName, Object[] arguments){ if( object instanceof String ) use(MyCategory){ assert object.&categorize() == "categorized: $object" } 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').categorize() == 'intercepted: categorized: golden' } Thread.start{ //no interception in spawned thread... use(MyCategory){ assert new String('bronze').categorize() == 'categorized: bronze' } } } </pre></td></tr></table> <h3>Unintercepted Interceptors</h3> <p>The special & notation for bypassing interceptors handles simple code, but for more complex code we often need our own UninterceptedInterceptor:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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 } } 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 } </pre></td></tr></table> <h3>Intercepting many classes in one block</h3> <p>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 the previous example.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import org.codehaus.groovy.runtime.InvokerHelper abstract class AliasInterceptor extends UninterceptedInterceptor{ protected aliases= [:] private toReturn= null, toThrow= false, toInvoke= false Object doBefore( Object object, String methodName, Object[] arguments ){ if( methodName in aliases.keySet() ) toReturn= InvokerHelper.invokeMethod( object, aliases[methodName], arguments ) //use Spanish names instead else if( methodName in aliases.values() ) toThrow= true //disable the English names else toInvoke= true //run other methods unchanged null } Object doAfter( Object object, String methodName, Object[] arguments, Object result ){ if( toReturn != null ){ result= toReturn toReturn= null }else if( toThrow ){ toThrow= false throw new MissingMethodException( methodName, object.getClass(), arguments ) }else toInvoke= false result } boolean doInvoke(){ toInvoke } } class ArrayListAliasInterceptor extends AliasInterceptor{ {aliases.putAll( [tamano:'size', todos:'each' ] )} //Spanish aliases } class HashMapAliasInterceptor extends AliasInterceptor{ {aliases.putAll( [tamano:'size', todos:'each' ] )} } class LinkedHashMapAliasInterceptor extends AliasInterceptor{ {aliases.putAll( [tamano:'size', todos:'each' ] )} } </pre></td></tr></table> <p>We call the code like so:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> def useAliasing= { Closure c-> useInterceptor(ArrayList, ArrayListAliasInterceptor){ useInterceptor(HashMap, HashMapAliasInterceptor){ useInterceptor(LinkedHashMap, LinkedHashMapAliasInterceptor){ c() } } } } useAliasing{ def a= [1, 3, 5, 7, 9] println 'size: '+ a.tamano() //Spanish 'tamano' is an alias for the 'size' method try{ println a.size(); assert 0 } catch(e){ assert e instanceof MissingMethodException } //English 'size' method disabled a.todos{ println 'item: '+ it } println '' def b= [a:1, c:3, e:5, g:7] println 'size: '+ b.tamano() try{ println b.size(); assert 0 } catch(e){ assert e instanceof MissingMethodException } b.todos{ println 'item: '+ it } println '' def c= new LinkedHashMap( [e:5, g:7, i:9] ) println 'size: '+ c.tamano() try{ println c.size(); assert 0 } catch(e){ assert e instanceof MissingMethodException } c.todos{ println 'item: '+ it } } </pre></td></tr></table> <p>We can put the cascadingly indented code into a list to make it neater by defining a utility category method on the List class.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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 } } } </pre></td></tr></table> <h3>Our own ProxyMetaClass</h3> <p>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:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import org.codehaus.groovy.runtime.InvokerHelper public class MyProxyMetaClass extends MetaClassImpl{ protected adaptee= null def interceptor= null MyProxyMetaClass(MetaClassRegistry registry, Class theClass, MetaClass adaptee){ super(registry, theClass); this.adaptee = adaptee } static getInstance(Class theClass){ def metaRegistry = InvokerHelper.getInstance().getMetaRegistry() new MyProxyMetaClass(metaRegistry, theClass, metaRegistry.getMetaClass(theClass) ) } void use(Closure closure){ registry.setMetaClass(theClass, this) 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) } ) } 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 (null == interceptor){ return howToInvoke.call() } interceptor.aroundInvoke(object, methodName, arguments, howToInvoke) } } interface AroundInterceptor{ Object aroundInvoke(Object object, String methodName, Object[] arguments, Closure proceed) } </pre></td></tr></table> <p>We can then run our code:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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 */ </pre></td></tr></table> <h3>Using many Interceptors with our own ProxyMetaClass</h3> <p>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:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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 } } </pre></td></tr></table> <h3>Using a MultiInterceptorProxyMetaClass for the Observer pattern</h3> <p>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.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> abstract class ObserverProtocol implements Interceptor{ private perSubjectObservers protected getObservers( subject ){ if( perSubjectObservers == null ) perSubjectObservers= [:] def observers= perSubjectObservers[ subject ] if( observers == null ){ observers= [] perSubjectObservers[ subject ]= observers } observers } public void addObserver( subject, observer ){ getObservers(subject) << observer } public void removeObserver( subject, observer ){ getObservers(subject).remove(observer) } abstract Object beforeInvoke( Object object, String methodName, Object[] arguments ) abstract boolean doInvoke() abstract Object afterInvoke( Object object, String methodName, Object[] arguments, Object result ) } </pre></td></tr></table> <p>We can extend this ObserverProtocol with domain-specific observers. The example is a Groovy rewrite of one first implemented in AspectJ by Jan Hannemann and Gregor Kiczales.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> public class Screen{ //class to be observed def name public Screen( String s ){ this.name= s } public void display( String s ){ println(this.name + ": " + s) } } public class Point{ //class to be observed def x, y, color public Point( int x, int y, Color color ){ this.x=x this.y=y this.color=color } } class ColorObserver extends ObserverProtocol{ 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 Point && methodName == 'setColor' ){ getObservers(object).each{ it.display("Screen updated (point subject changed color).") } } result } } class CoordinateObserver extends ObserverProtocol{ 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 Point && ['setX', 'setY'].contains(methodName) ){ getObservers(object).each{ it.display("Screen updated (point subject changed coordinates).") } } result } } class ScreenObserver extends ObserverProtocol{ 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 Screen && methodName == 'display' ){ getObservers(object).each{ it.display("Screen updated (screen subject changed message).") } } result } } </pre></td></tr></table> <p>Now we run the program. It first creates five Screen objects (s1, s2, s3, s4, and s5) and one point object, then sets up some observing relationships (namely, s1 and s2 will observe color changes to the point, s3 and s4 will observe coordinate changes to the point, and s5 will observe s2's and s4's display method), and finally, make changes to the point, first, the color, then its x-coordinate. The color change triggers s1 and s2 to each print an appropriate message. s2's message triggers its observer s5 to print a message. The coordinate change triggers s3 and s4 to print a message. s4's message also triggers the observer s5.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> 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. */ </pre></td></tr></table> <h3>Using a MultiInterceptorProxyMetaClass and UninterceptedFriendlyInterceptor for the Decorator pattern</h3> <p>We can use more than one unintercepted 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 UninterceptedFriendlyInterceptor, that can be used as one of many with the MultiInterceptorProxyMetaClass.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> abstract class UninterceptedFriendlyInterceptor implements Interceptor{ def proxy= null abstract Object doBefore( Object object, String methodName, Object[] arguments ) public Object beforeInvoke(Object object, String methodName, Object[] arguments){ def theInterceptors= proxy.interceptors proxy.interceptors= null def result try{ result= doBefore(object, methodName, arguments) }catch(Exception e){ throw e }finally{ proxy.interceptors= theInterceptors } 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){ def theInterceptors= proxy.interceptors proxy.interceptors= null try{ result= doAfter(object, methodName, arguments, result) }catch(Exception e){ throw e }finally{ proxy.interceptors= theInterceptors } result } } </pre></td></tr></table> <p>For our example Decorator pattern, we'll code an OutputStreamWriter that prints extra if necessary. We use decorators extended from the UninterceptableFriendlyInterceptor. Firstly, a NewlineDecorator that uses a line-width policy to perhaps place the output on a new line. And second, a very simple WhitespaceDecorator that ensures there's some whitespace between any two consecutive items output. Each has only very simple logic for this example.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> abstract class PrintDecorator extends UninterceptedFriendlyInterceptor{ abstract Object doBefore( Object object, String methodName, Object[] arguments ) abstract Object doAfter( Object object, String methodName, Object[] arguments, Object result ) //only execute the intercepted method if it's the last class in the chain of //decorators around the method... boolean doInvoke(){ proxy.interceptors[-1] == this } } class NewlineDecorator extends PrintDecorator{ int lineSizeSoFar= 0 Object doBefore( Object object, String methodName, Object[] arguments ){ if( methodName == 'leftShift' && arguments[0] instanceof String ){ if( lineSizeSoFar + arguments[0].size() > 30){ arguments[0]= '\r\n' + arguments[0] lineSizeSoFar= 0 }else{ lineSizeSoFar += arguments[0].size() } } } Object doAfter( Object object, String methodName, Object[] arguments, Object result ){ result } } class WhitespaceDecorator extends PrintDecorator{ def prevOutput= ' ' Object doBefore( Object object, String methodName, Object[] arguments ){ if( methodName == 'leftShift' && arguments[0] instanceof String ){ if( prevOutput[-1] != ' ' && prevOutput[-1] != '\n' ){ arguments[0] = ' ' + arguments[0] } } } Object doAfter( Object object, String methodName, Object[] arguments, Object result ){ if( methodName == 'leftShift' && arguments[0] instanceof String ){ prevOutput= arguments[0] } result } } </pre></td></tr></table> <p>After the classes, interceptors, and code block are matched up, the printing logic and the OutputStreamWriter are both unaware that the output is being decorated. Each decorator will perhaps modify the output, then pass it along to the next decorator to do the same. The distinct items of output sent to the OutputStreamWriter are separated by spaces, whether or not a space was in the output string in the program, and the output fits within a certain width.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> oswProxy= MultiInterceptorProxyMetaClass.getInstance( OutputStreamWriter ) [ new NewlineDecorator(), new WhitespaceDecorator(), //the order of these decorators is important ].each{ it.proxy= oswProxy oswProxy.interceptors << it } oswProxy.use{ def wtr= new OutputStreamWriter( new FileOutputStream( new File('TheOutput.txt') ) ) wtr<< "Singing in the Rain" << "hello " << "climate " << "hotrod" << "far out and spacy" << 'Clementine, darling' wtr.close() } /*output file: Singing in the Rain hello climate hotrod far out and spacy Clementine, darling */ </pre></td></tr></table>
Please type the word appearing in the picture.
Attachments
Labels
Location
Watch this page
< Edit
Preview >
Loading…
Save
Cancel
Next hint
search
attachments
weblink
advanced