Ben Nadel posted an interesting article over on his blog titled Instantiating Groovy Classes In The ColdFusion Context where he demoed how to create a class factory in Groovy and invoke it from CFML to insantiate new instances of Groovy classes without actually reentering a Groovy context. I wanted to expound on what he demoed a bit more.
First and foremost, these two snippets are identical in functionality:
<cfset variables.map = createObject("java", "java.util.HashMap") />
<g:script script="variables.map = new HashMap()" />
After either line executes, 'variables.map' will contain a HashMap instance that is a full-fledged citizen of the JVM. The way it was created is irrelevant. This goes for ANY Java object, whether it's a CFC, Groovy, core Java, etc. That why Ben's code works; once his factory is created, it can just do it's thing. The context that it's methods are invoked in is irrelevant becaues the object itself doesn't change based on context.
I'm not sure I recommend a generic factory like this for real applications, though it's certainly good for experimentation. For actual apps, I usually write one or more singletons (often DAOs) in Groovy that fulfill both a business role in the app as well as the VO creation role. Then you have newPerson(name, hair, gender) rather than get("Person").init(name, hair, gender) with a proxy and a pile of reflection.
What makes this even more interesting is that because your objects are Groovy objects, they have the Groovy neatness available to them. In particular, implicit getters. If you write your persistence layer with Groovy (but not Hibernate), implicit getters can easily synthesize the transitive recall that your Hibernate-managed objects get for free.
At work, we do a lot of stuff on a JSR-270 Java Content Repository (with Magnolia CMS), which can't be fronted by Hibernate, but using Groovy I still get lazy loading across entity relationships. My DAO (written in Groovy) has this method for retrieving a list of newsletters:
def getNewsletterList(int limit = 999999999) {
def nodes = daoHelper.query("/*[MetaData/mgnl:template='newsletter']").reverse()
if (nodes.size() > limit) {
nodes = nodes.subList(0, limit)
}
nodes.collect { new Newsletter(daoHelper, it) }
}
And the Newsletter class has this implicit getter:
def _articles def getArticles() { if (! _articles) { _articles = daoHelper.query("/*[jcr:uuid='$uuid']/*[MetaData/mgnl:template='newsletter_article']") .collect { new Article(daoHelper, it) } } _articles }
When first invoked, it'll use daoHelper (which was passed to the Newsletter in the first snippet) to pull out a list of article nodes from the JCR and inflate them into Article instances. That list of instances is cached in '_articles' so it doesn't have to be created multiple times. This lets me grab a newsletter, and if needed, lazy load the newsletter's articles without paying the cost of loading the articles if I'm not going to need them. But even better is the CFML that uses these objects.
Here's a snippet that renders a list of newsletters (retrieved via getNewsletterList) with a list of articles:
<ul class="issue-list"> <cfloop array="#newsletters#" index="newsletter"> <li><h3 class="title"><a href="#newsletter.handle#">#newsletter.title#</a></h3> <h4>In This Issue:</h4> <ul> <cfloop array="#newsletter.articles#" index="article"> <li><a href="#article.handle#">#article.title#</a></li> </cfloop> <li class="full"><a href="#newsletter.handle#">View Full Issue</a></li> </ul> </li> </cfloop> </ul>
What you'll notice is that I'm treating the 'newsletters' variable as if it were an array of structs, when it's actually an array of Groovy instances. In particular, notice the way I loop over the articles. No method call, it's just a property. This is ridiculously powerful, because it lets you flip-flop between properties and getters/setters at will without affecting calling code.
This code doesn't illustrate it, but I can traverse much more deeply that this. Say for each article I wanted to list the distinct set of categories that the article's author has posted at least one article in. Easy:
cats = article.author.articles.sum({ it.categories }).unique
So this will hit the implicit getAuthor() method of Article to lazy-load an author, then call getArticles() on Author to get all of their articles, then iterate over them and calling getCategories() on each one and adding all the collections together, and then finally pull unique values. The result will be a list of Category objects which can be further traversersed:
newslettersForFirstCategory = cats.first().articles.collect({ it.newsletter }).unique
This sort of traversal might seem really hard to read, but it's easily picked up and becomes very intuitive. You start at the left, and each step transforms your initial data into a new piece of data and passes it down the chain.
It is worth mentioning that just like Hibernate, this sort of lazy loading does result in the N+1 query problem. Tune your database, optimize your SQL, and you probably won't have a problem.
I know that in the long run, Reflection is never a great thing, but seeing as I just started looking into this stuff, I couldn't figure out what else to do. I would have liked to do something like what ColdFusion provides where I can use CFInvoke or CreateObject() to instantiate objects based on name.
Also, I couldn't figure out how to work very well with variable number of arguments; I expect that no one really makes this as easy as ColdFusion.
I really like what you're saying, though, about the implicit getters and setters. Very cool stuff and it really opens the door up for changing things later on.
CFINVOKE and createObject() are both backed by reflection, almost identical to the way you implemented it in your code. Obviously it supports variable numbers of arguments in a cleaner way (not a list of ten), but the gist of it is identical. Check out the first example at http://www.javalobby.org/articles/groovy-intro3/ for some goods about variable numbers of arguments. It's just as easy (perhaps easier) as with CFML. :)
Ah, ok, cool. I just figured CFInvoke / CFInvokeArgument was some sort of more elegant dynamic code execution. I'll be sure to check out that link.
And, again, thanks for the CFUNITED presentation – it was very inspiring.
Actually, I think I read that link this morning! I think there is a problem – the variable arguments seemed to work perfectly fine from within Groovy; but, I think when called from ColdFusion:
… I was method signature errors that CF was trying to find this:
method( Object[] )
It looks like within the Groovy context, N methods => Object[] args happens implicitly; but, when going from CF to Groovy, you need to explicitly define the collection.
Of course, again, I am new – I might very well be way off here. But, that's why I went with the 10 explicit args rather than (Object… args) notation.
Yeah, you're right. It's actually a compiler hack that does it, not a runtime features, and CF doesn't have it. E.g. when you write myVarargsMethod(1, 2, 3, 4), it actually compiles down to the equivalent of myVarargsMethod([1, 2, 3, 4].toArray()), so at runtime it's actually a single Object[] that is passed to the method. CF can't do that because it's reflection support is apparently still in Java 1.4 land.
You could use missingMethod to implement your init() method; then you'll get the arguments as a collection along with the method name.
I was looking up missingMethod and some articles said that it was very slow. Is this true? I only ask because in the CF world, it has been explained that since CF checks for the target method before sending the message to OnMissingMethod(), it actually has almost no overhead (and no exception handling). Does Groovy work differently? Or, is the "slow" that Groovy is talking about relative?
I honestly don't know. I'd expect missingMethod to be significantly slower than a "real" method invocation, but with how much faster Groovy operations are than CFML ones, I really can't say if it's relevant. If a real method call takes 5 nanoseconds, and a missingMethod call takes 500 nanoseconds, that's 100 times slower. But if a normal CF method call also takes 500 nanoseconds, then Groovy's missingMethod is fast. Those numbers are completely baseless, they're merely for illustrative purposes. You'd have to do testing to see if the performance actually mattered.
Where did you read that onMissingMethod() was slow? The actual invocation is very fast because it's built right into the function lookup so it adds only a few ms to the call. Of course, performance depends on what you do *inside* onMissingMethod() …
I've used onMissingMethod() very successfully to provide automatic delegation across tiers in an application as well as generic CRUD methods which cuts out huge amounts of boilerplate code. Yes, there's an overhead in the logic *inside* oMM() that decodes what the method should do and how to process the 'real' call but in the grand scale of things the RAD benefits overwhelmed that completely for Phase I of a project…
@Sean,
I was saying that onMissingMethod() was *not* slow. In fact, I think I was actually thinking of a conversation that you and I had (online) in regards to that. I was saying that I was surprised that Groovy's missingMethod() was considered slow, since CF's was considered fast.
Ah, I misread you, sorry. Again, I don't think Groovy's missingMethod() is slow per se but what you then do in order to process the 'missing' method call may well be slow. Of course, in Groovy it can optimize 'regular' method calls down to native Java calls so compared to that, both Groovy's missingMethod() and *any* of CFML's method calls are slow since they look a lookup and, potentially, reflection…
I've been trying to play around with Groovy, but I feel so handicap because I am trying to, perhaps, get too dynamic without truly knowing much about the language. Everything in CF is so easy, it seems, but I guess it's just that I don't know the syntax in Groovy yet.
This morning, I tried to play around with two ideas that stopped me dead in my tracks:
1. Extending CF classes
2. Overwriting cast operators
1 – its seems that a lot of the CF classes are considered "final"?? and Groovy will not let me extend them. :(
2 – this might just be something that is not possible across the groovy / cf context. Plus, I probably don't have the correct syntax.
… sorry, I know this comment is a total non-sequitur.