问题
I've got a HashMap as Map<Long, List<Map<String, Object>>> typeAndKno
, in the FreeMarker page, I get the content of this map like this:
<#list typeAndKno?keys as typeId>
${typeAndKno.get(typeId).get(0).get('TYPE_NAME')}
<#list typeAndKno.get(typeId) as kno>
${kno.get('KNOWLEDGE_ID')}
</#list>
</#list>
This code works fine in Struts2, but after moved to Spring MVC, the code fails. I finally changed the code to this:
<#list typeAndKno?keys as typeId>
${typeAndKno[typeId]?first['TYPE_NAME']}
<#list typeAndKno[typeId?string] as kno>
${kno['KNOWLEDGE_ID']}
</#list>
</#list>
What's the difference between these two pieces of code? Is there a way to make the first piece of code work in Spring MVC?
回答1:
Update:
As of 2.3.22 there's a much easier and non-disruptive solution for this: configure FreeMarker so that ?api
works, and then you can use the Java API of Map
where the keys aren't String
-s. See this FAQ entry or this answer for more details.
And Strut's FreeMarker setup is something that's strongly discouraged now. Of course, back then, when they did that, that was maybe the most reasonable workaround, but it isn't anymore for a while, and especially not since 2.3.22.
Old answer (outdated):
The way you see Java objects from templates depends on the ObjectWrapper
used, which is a FreeMarker configuration setting. Based on your example, Struts uses a BeansWrapper
with its default settings, while Spring possibly uses the DefaultObjectWrapper
. So that causes the differences. I wouldn't recommend using either, because:
With
BeansWrapper
with its default settingsMap
keys mix with the method names, with method names having priority. Surely you can safely usemyMap.get(key)
to get around that, butmyMap.get('foo')
is just horrible compared tomyMap.foo
, which will only work as far you have no method calledfoo
. Also?keys
will return a mixture of real keys and method names with it... it's a mess.With
DefaultObjectWrapper
you can safely writemyMap.foo
, but you won't be able to get entities with non-string keys, becausemyMap[key]
only support strings, and you don't havemyMag.get(key)
anymore.
So what I have usually used with FreeMarker was a bw = new BeansWrapper(); bw.setSimpleMapWrapper(true)
. With this, the methods of Map
-s are not visible, just like with DefaultObjectWrapper
, so you can use myMap.foo
and myMap[key]
safely. But if rarely you need to get something with a non-string key, you can use myMap(nonStringKey)
(yes, with ()
instead of []
). This last doesn't work with DefaultObjectWrapper
. (Hopefully FreeMarker 2.4 will solve this mess with non-string keys nonsense, but it's not like it will be out anytime soon...)
So the next question is how to set the object wrapper with Spring. I'm not at home there. As far as I see, you have a FreeMarkerConfigurer
bean which has a freemarkerSettings
property, which is a Properties
object that's eventually is passed to FreeMarker's Properties
-based configuration API. So there you should be able add an object_wrapper
property that refers to the class name of the ObjectWrapper
to use (or it could just be beans
to use the default BeansWrapper
instance like maybe Struts does). Problem is, the property-based API is rather limited, and so you can't both create and configure (call setSimpleMapWrapper
) a BeansWrapper
there. You could do that in the spring configuration file of course, but I don't see a way to inject that into the FreeMarkerConfigurer
, unless you create the whole freemarker.template.Configuration
object as a bean, and inject that into the FreeMarkerConfigurer
with the configuration
property of it. So maybe the easiest workaround is extending BeansWrapper
to override the default of simpleMapWrapper
, and then referring to the class of that extending class via object_wrapper
. (Just in case somebody reads this later, it's probable that FreeMarker 2.3.21 will extended the properties configuration API so that you can just set object_wrapper
to BeansWrapper() { simpleMapWrapper = true }
.)
来源:https://stackoverflow.com/questions/18449159/freemarker-complex-collection