Groovy's invokeMethod allows Groovy code to intercept a method call and either do something different than the intercepted method, do something before invoking the intended method, or do something after invoking the intended method. This, of course, sounds a lot like aspect-oriented programming (AOP). This approach works particularly well for adding functionality of
toString()
, equals(Object)
, and hashCode()
to classes that don't already override these methods (such as the JAXB-generated classes). We can intercept calls to the versions of these methods provided by the super Object class and instead implement versions specific to the JAXB-generated classes.The next Groovy code listing shows Groovy code that intercepts calls to common methods on the JAXB-generated
MovieType
class (see the previous post for this class's source code).compareMovieTypes4.groovy
import dustin.examples.MovieGenre import dustin.examples.MovieType import dustin.examples.MovieTypeCommons import dustin.examples.Movies import javax.xml.bind.Unmarshaller import javax.xml.bind.JAXBContext import javax.xml.bind.JAXBException MovieType.metaClass.invokeMethod = { String name, args -> if (name == 'equals') { return MovieTypeCommons.doEquals(delegate, args[0]) } else if (name == 'hashCode') { return MovieTypeCommons.doHashCode(delegate) } else if (name == 'toString') { return MovieTypeCommons.doToString(delegate) } else { def methodHandle = MovieType.metaClass.getMetaMethod(name, args) if (methodHandle != null) { return methodHandle.invoke(delegate, args) } } } def movies1 = getContentsOfMoviesFile("movies1.xml") def movies2 = getContentsOfMoviesFile("movies1.xml") movies1.movie.each { movie -> boolean matchFound = false movies2.movie.each { otherMovie -> if (movie == otherMovie) { matchFound = true } } if (matchFound) { println "Match FOUND for ${movie.toString()}" } else { println "NO match found for ${movie.toString()}" } } def Movies getContentsOfMoviesFile(String xmlFileName) { def jc = JAXBContext.newInstance("dustin.examples") def u = jc.createUnmarshaller() def movies = (Movies) u.unmarshal(new File(xmlFileName)) return movies; }
The simple Groovy script shown above uses the syntax
MovieType.metaClass.invokeMethod = {
to declare a closure that intercepts all method calls on the MovieType
class. In this simple script, any method with the name "toString", "equals", or "hashCode" is intercepted and each method with one of these names has its implementation replaced with a call to the MovieTypeCommons
class introduced in this earlier post. Any intercepted method that does not have a name matching "toString", "equals", or "hashCode" is not treated and the code simply turns around and invokes that intended method without change. The gist of all this is that only methods with the three mentioned names are changed in implementation (in this case to override the nearly useless Object
-provided implementation with something useful) and all other methods are left well enough alone.The Groovy metaClass for
MovieType
is used to invoke invokeMethod to intercept method calls. This same metaClass is also used to get a handle on the method that was intercepted so that this handle's invoke
method can be used to invoke the originally intended method in the manner that it was originally called.After the closure for
MovieType
is defined, the script goes on to use the conveniences of this method interception to invoke equals
(via Groovy's ==
operator) and toString()
on the MovieType
class. The good news here is that this script does determine that JAXB objects with the same content are equal and does provide a nice String representation of them thanks to the intercepted and alternatively handled methods.There are different flavors of method interception in Groovy. The GroovyInterceptable interface is defined in its Javadoc documentation as a "marker interface used to notify that all methods should be intercepted through the invokeMethod mechanism of GroovyObject." Making a Groovy class implement this interface provides similar method interception as the approach I demonstrated earlier in this post using
metaClass.invokeMethod
.If the code needing to use JAXB-generated classes is Groovy, this is an elegant way to "add" methods to those classes for common functionality such as equality checking, hash code generation, and String representation. Of course, this approach can be generalized to cover any such situations when existing classes (even Java classes like JAXB-generated
MovieType
) need to support methods they don't statically support.
No comments:
Post a Comment