Spring extensible XML and web controllers
So, having got spring XML config extensions working quite nicely, I thought I’d have a go at another area of our codebase that’s been bugging me.
We have a few areas where we re-use the same MVC controller many times, with a different command and a different view. So the config looks something like this:
<bean name="/view/userbreakdown.htm" id="userBreakdownController" class="uk.ac.warwick.sbr.web.hitlog.HitLogStatsController"> <property name="commandClass" value="uk.ac.warwick.sbr.hitlog.HitLogStatsUserInfoBreakdownCommand"/> <property name="statsView" value="hitLogUserBreakdownView"/> </bean> <bean name="/view/ipbreakdown.htm" id="ipBreakdownController" class="uk.ac.warwick.sbr.web.hitlog.HitLogStatsController"> <property name="commandClass" value="uk.ac.warwick.sbr.hitlog.HitlogStatsIPInfoBreakdownCommand"/> <property name="statsView" value="hitLogIPBreakdownView"/> </bean> ... continue for many more HitLogStatsControllers...
Hmm, I thought, would’t this be nicer if we could just say
<stats:controller name="/view/all.htm" id="summaryController" command="HitLogStatsSummaryCommand" view="hitLogAllView"/>
Well, it turns out to be harder than you might think. As you can see from the bean definitions, we’re using the BeanNameUrlHandlerMapping to let SMVC map requests onto controllers. This relies on setting the name attribute of a bean to the URL you want (You can’t use the ID, because slashes are illegal for ID attributes). N.b. this is an attribute of the bean def; not a property of the bean.
So, we need to set the bean name. But, this doesn’t appear possible using
NamespaceHandlerSupport. The name isn’t actually an attribute of the BeanDefinition itself, rather it’s part of the BeanDefinitionHolder class. You set it using the 3-arg constructor of BeanDefinitionHolder. Alas, all beans defined by non-default XML have their BeanDefinitionHolders created for them in AbstractBeanDefinitionParser.parse, which calls the 2-arg version of the constructor (which doesn’t set a beanName). Default XML elements, by comparison, are created in BeanDefinitionParserDelegate, which uses the 3-arg version.
So, can we fix it? Making the custom parsing code call the 3-arg constructor would involve ripping a great deal of the guts of the XML parsing code out; not something I’d be too keen on. Maybe I should raise a JIRA with the Spring MVC team.
An easier solution might be to write a different HandlerMapping, that used a bean property (“path”, say) rather than bean names/aliases to store the URL path in. This strikes me as a nicer solution (not least because it doesn’t overload the bean name with behaviour that’s nothing to do with names), though I don’t know whether it would perform as well ( lookups presumably get cached,though, so it would be a one-off cost).
Alternatively, I could convert the one-controller-several-commands model into several ThrowawayControllers (all inheriting a common base), and then use the annotation-based config to set them all up. This seems like it might be a neater long-term solution, so long as there’s nothing that’s too expensive to set up in the controllers (which can’t be pushed out into an injected service).