Given this grossly simplified rendition of the setup:
package net.myexample.plugin
class MyExampleService {
Map doMunge(Map m) {
// do stuff to 'm'
return m
}
}
/****************************** BREAK: NEXT FILE ******************************/
package net.myexample.plugin
class MyTagLib {
static namespace = 'p'
def myExampleService
def tag = { attrs, body ->
def m = doMungeAndFilter(attrs.remove('m'))
out << g.render(template: '/template', plugin: 'my-example-plugin', model: m)
}
Map doMungeAndFilter(def m) {
def mm = myExampleService.doMunge(m)
// do stuff to 'm'
return mm
}
}
/****************************** BREAK: NEXT FILE ******************************/
package net.myexample.app
import net.myexample.plugin.MyExampleService
class MyExampleService extends net.myexample.plugin.MyExampleService {
def doMunge(def m) {
def mm = super.doMunge(m)
// do more stuff to 'mm'
return mm
}
}
/****************************** BREAK: NEXT FILE ******************************/
package net.myexample.app
import net.myexample.plugin.MyTagLib
class MyTagLib extends net.myexample.plugin.MyTagLib {
static namespace = 'a'
def myExampleService
def tag = { attrs, body ->
def m = doMungeAndFilter(attrs.remove('m'))
out << g.render(template: '/template', plugin: 'my-example-plugin', model: m)
}
Map doMungeAndFilter(def m) {
def mm = super.doMungeAndFilter(m)
// do more stuff to 'mm'
return mm
}
}
/**
* But we get an exception that cites that it cannot call 'doMunge' on a null
* object -- which could only be 'myExampleService'
*/
Why would the service appear to be null when the method on the app’s taglib calls its superclass (the taglib on the plugin), which in turn calls the method on the service?
The best theory I could come up with is that the service is not actually being instantiated in the app’s taglib class because there are no explicit references to it aside from the def. I presume that this is the case because if I move all the logic from service class’s method into the taglib’s method, it works as expected.
(For the sake of painting a complete picture: MyExampleService.doMunge is called in other places, whereas the subsequent filtering (in MyTagLib.doMungeAndFilter) is only needed for the taglib.)
Alternatively: if I move doMungeAndFilter into another service class, creating the base version in the plugin and extending it in the app, that works fine. Which I suppose is an acceptable conclusion, though it feels like bloat to create another service class just to support the taglib like that.
Thoughts? Tips? Glaring errors or omissions?
Remove the
def myExampleServicefrom the subclass taglib. A property like that in Groovy compiles to a private field plus a public getter and setter, so in the superclass taglib you have implicitlyWhen you declare
myExampleServiceagain in the subclass the subclass gets its own private field (with the same name) and the setter gets overridden to store the supplied value in this subclass field instead of the superclass one. Spring calls the setter to inject the service, so the end result is that the superclass privatemyExampleServicenever gets set, hence the null pointer exception when trying to callmyExampleService.doMungein the superclass.The subclass has access to the superclass property via the inherited getter and setter so it doesn’t need to re-declare it.