1

In Grails you can declare a controller action like this:

def create(Integer foo, Integer bar) {
}

And if your HTTP request has parameters named foo and bar with values that can be converted to an Integer, the parameters will be assigned these values. I'm wondering how Grails can do this, because my understanding is that at the JVM bytecode level, a method's formal parameter names are not available. Is this witchcraft or am I misunderstanding something?

Dónal
  • 185,044
  • 174
  • 569
  • 824
  • I would say that, in general, frameworks = witchcraft ;) – doelleri Mar 05 '14 at 20:53
  • They are available if the compiler includes optional metadata. – Antimony Mar 05 '14 at 20:59
  • @Antimony I doubt this feature relies on the compiler providing optional metadata – Dónal Mar 05 '14 at 21:06
  • @Don well that's how you get the parameter names. – Antimony Mar 05 '14 at 22:12
  • @Antimony but this would mean that this feature of Grails relies on this *optional* metadata, so presumably it would fail if this metadata is not provided. Anyway, according to the answers this is implemented via an AST. – Dónal Mar 06 '14 at 00:42
  • @Don It's relying on the compiler either way. Anyway, I'm just pointing out that sometimes it is possible to get the parameter names, even in Java. – Antimony Mar 06 '14 at 02:20

2 Answers2

3

Basically what happens is that there's an AST transform that adds a new method with no args and the same name. This new method has logic in it to do the data binding based on the declared types of your "real" method, and then call your method. That's why the types are required (otherwise there's no way to do a conversion) and why you cannot have method overloads.

The inability to have overloaded methods is easy to work around though. Say you wanted an action

def foo(String bar)

and another

def foo(String bar, Integer wahoo)

In this scenario just keep the 2nd method and check to see if wahoo is null.

It's also important to use object parameter types and not primitives. If you use int/long/boolean/etc. and there is no provided parameter, you would get a NPE (since zero is not an acceptable conversion from null for numbers, and either is false for booleans).

You can get a decent sense for what's going on if you decompile the class using JD-GUI or another decompiler.

Dónal
  • 185,044
  • 174
  • 569
  • 824
Burt Beckwith
  • 75,342
  • 5
  • 143
  • 156
  • Thanks a lot, very interesting. I didn't know you can't have overloaded controller actions, it's not something I've ever needed. Does JD-GUI do a good decompiling Groovy code, I wouldn't have expected this, particularly a controller class that is (presuably) heavily metaprogrammed/AST transformed – Dónal Mar 06 '14 at 00:48
  • I just noticed that you said "This new method has logic in it to do the data binding based on the declared types of your "real" method". Presumably the action's arg names are also used, because otherwise how would you know which param to assign to which arg if the action has multiple args of the same type? – Dónal Mar 06 '14 at 00:55
  • Right, that information is available from the method signature. – Burt Beckwith Mar 06 '14 at 01:12
  • Am I right in saying that it's available from the method signature in an AST transform, but not in the bytecode, e.g. via reflection? – Dónal Mar 06 '14 at 01:50
  • The original method isn't removed, it's still there. In general it's not possible to get argument names via reflection because they're not important in bytecode, but if you compile with debug enabled (and Grails does) then they're available - e.g. see http://stackoverflow.com/questions/6759880/getting-the-name-of-a-method-parameter – Burt Beckwith Mar 06 '14 at 02:07
1

The fact that Grails controllers are Groovy classes helps quite a lot. Looking through the source code for controllers you can see where it makes heavy use of AST transformations, in particular the MethodNode. So, before it becomes bytecode the "witchcraft" is done. :)

Joshua Moore
  • 24,706
  • 6
  • 50
  • 73