Freemarker中,使用非字符串作为Map的Key,读取不到Value的问题

Freemarker中,使用非字符串作为Map的Key,读取不到Value的问题

使用freemarker作为模版引擎。尝试对一个非字符串作为KeyMap进行循环渲染的时候,会出现读取不到value问题

案例演示

通过一个Map,按照性别对用户进行分组。性别的定义使用一个枚举,而不是字符串。然后渲染在页面上。

public static enum Gender {
	BOY,
	GIRL
}
Map<Gender, List<String>> models = new HashMap<>();

Controller

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;



@RequestMapping("/test")
@RestController
public class TestController {

	public static enum Gender {
		BOY,
		GIRL
	}
	
	@GetMapping
	public ModelAndView test (HttpServletRequest request, HttpServletResponse response) throws IOException {
		
		ModelAndView modelAndView = new ModelAndView("test/test");
		
		Map<Gender, List<String>> models = new HashMap<>();
		models.put(Gender.BOY, Arrays.asList("小明", "中明", "大明"));
		models.put(Gender.GIRL, Arrays.asList("小红", "中红", "大红"));
		modelAndView.addObject("users", models);
		
		return modelAndView;
	}
}

View

先遍历Map中的key,再根据key获取到value,对value进行遍历

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8"/>	
		<title>freemarker</title>
	</head>
	<body>
		<#list users?keys as gender>
			<h3>${gender}</h3>
			<ul>
				<#list users[gender] as user>
					<li>${user}</li>
				</#list>
			</ul>
		</#list>
	</body>
</html>

异常

Caused by: freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
==> users[gender]  [in template "test/test.ftl" at line 11, column 40]

----
Tip: It's the final [] step that caused this error, not those before it.
----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
	- Failed at: #list users[gender] as user  [in template "test/test.ftl" at line 11, column 33]
----

异常信息表示,根据key读取到的value是空。也就是这个表达式:users[gender]

解决办法1 - 使用字符串做为Map的key

Controller

修改MapKey为,枚举的 name() 属性,它是字符串。

Map<String, List<String>> models = new HashMap<>();
models.put(Gender.BOY.name(), Arrays.asList("小明", "中明", "大明"));
models.put(Gender.GIRL.name(), Arrays.asList("小红", "中红", "大红"));
modelAndView.addObject("users", models);

渲染结果 - 正常

image

解决办法2 - 修改Freemarker的设置

对 Configuration 进行设置

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import freemarker.template.DefaultObjectWrapper;
import freemarker.template.TemplateModelException;

@Configuration
public class FreemarkerConfiguration {

	@Autowired
	private freemarker.template.Configuration configuration;
	
	@PostConstruct
	public void configuration() throws TemplateModelException {
		this.configuration.setAPIBuiltinEnabled(true);
		DefaultObjectWrapper defaultObjectWrapper = (DefaultObjectWrapper) configuration.getObjectWrapper();
		defaultObjectWrapper.setUseAdaptersForContainers(true);
	}
}

表达式需要修改

通过: map?api.get(key) 来获取到value

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8"/>	
		<title>freemarker</title>
	</head>
	<body>
		<#list users?keys as gender>
			<h3>${gender}</h3>
			<ul>
				<#list users?api.get(gender) as user>
					<li>${user}</li>
				</#list>
			</ul>
		</#list>
	</body>
</html>

渲染结果 - 正常

image

总结

处理这种问题,要么修改Mapkey为字符串。要么修改Freemarker的配置,以及取值表达式。

Freemarker配置

configuration.setAPIBuiltinEnabled(true);
DefaultObjectWrapper defaultObjectWrapper = (DefaultObjectWrapper) configuration.getObjectWrapper();
defaultObjectWrapper.setUseAdaptersForContainers(true);

取值表达式

<#list map?keys as key>
	${key?c} - ${map?api.get(key)}<br/>
</#list>