<!-- FreeMarker 核心 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version> <!-- 建議升級至較新版本 -->
</dependency>
<!-- Spring 對 FreeMarker 的支援 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.21</version> <!-- 對應 Spring 版本 -->
</dependency>
implementation 'org.freemarker:freemarker:2.3.32'
implementation 'org.springframework:spring-context-support:5.3.21'
tag_syntax=auto_detect
template_update_delay=0 # 開發環境設 0 即時生效,生產建議 >0 或 -1
default_encoding=UTF-8
output_encoding=UTF-8
locale=zh_TW # 繁體中文環境
date_format=yyyy-MM-dd
time_format=HH:mm:ss
datetime_format=yyyy-MM-dd HH:mm:ss
number_format=0.## # 數字預設格式
boolean_format=true,false
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="https://www.springframework.org/schema/mvc"
xsi:schemaLocation="
https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 1. 載入屬性檔 -->
<bean id="freemarkerConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:conf/freemarker.properties" />
</bean>
<!-- 2. FreeMarker 核心配置 -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/ftl/" />
<property name="freemarkerVariables">
<map>
<entry key="xml_escape" value-ref="fmXmlEscape" />
</map>
</property>
<property name="configuration" ref="freemarkerConfiguration" />
</bean>
<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape" />
<!-- 3. 視圖解析器 -->
<bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" />
<property name="viewNames">
<array><value>*.ftl</value></array>
</property>
<property name="contentType" value="text/html; charset=UTF-8" />
<property name="exposeRequestAttributes" value="true" />
<property name="exposeSessionAttributes" value="true" />
<property name="exposeSpringMacroHelpers" value="true" />
<property name="requestContextAttribute" value="request" />
<property name="order" value="1" /> <!-- 優先於 JSP 解析器 -->
</bean>
<!-- 靜態資源處理 (別忘了) -->
<mvc:resources mapping="/static/**" location="/static/" />
<mvc:annotation-driven />
</beans>
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
@Bean
public FreeMarkerConfigurer freemarkerConfig() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/ftl/");
configurer.setDefaultEncoding("UTF-8");
// 直接設定 Configuration 屬性
Properties props = new Properties();
props.setProperty("template_update_delay", "0");
props.setProperty("locale", "zh_TW");
props.setProperty("number_format", "0.##");
configurer.setFreemarkerSettings(props);
// 共享變數
Map<String, Object> variables = new HashMap<>();
variables.put("xml_escape", new XmlEscape());
configurer.setFreemarkerVariables(variables);
return configurer;
}
@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setViewClass(FreeMarkerView.class);
resolver.setViewNames(new String[]{"*.ftl"});
resolver.setContentType("text/html; charset=UTF-8");
resolver.setExposeRequestAttributes(true);
resolver.setExposeSessionAttributes(true);
resolver.setExposeSpringMacroHelpers(true);
resolver.setRequestContextAttribute("request");
resolver.setOrder(1);
return resolver;
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(freeMarkerViewResolver());
// 若同時支援 JSP
// registry.jsp("/WEB-INF/jsp/", ".jsp").order(2);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
}
@Controller
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/world")
public String helloWorld(Model model) {
model.addAttribute("word0", "Hello ");
model.addAttribute("word1", "World!");
model.addAttribute("users", List.of("Alice", "Bob", "Charlie"));
return "helloWorld"; // 對應 /WEB-INF/ftl/helloWorld.ftl
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>FreeMarker Demo</title>
</head>
<body>
<h2>${word0}${word1}</h2>
<h3>用戶列表 (list 指令)</h3>
<ul>
<#list users as user>
<li>${user_index}: ${user}</li>
</#list>
</ul>
<h3>條件判斷 (if/else)</h3>
<#if users?size > 2>
<p>用戶超過 2 人</p>
<#else>
<p>用戶不足 2 人</p>
</#if>
</body>
</html>
| 問題 | 解決方案 |
|---|---|
| 模板修改不生效 | 開發環境設 template_update_delay=0;生產設 -1 (不檢查) 或大於 0 秒 |
| 中文亂碼 | 確保 default_encoding=UTF-8、output_encoding=UTF-8、ViewResolver contentType 含 charset、模板檔案以 UTF-8 儲存 |
| 靜態資源 404 | 配置 <mvc:resources> 或 addResourceHandlers |
| 共用佈局/片段 | 使用 <#include>、<#import>、<@layout> 宏 |
| Spring 表單標籤 | 引入 <#import "/spring.ftl" as spring/> 使用 <@spring.formInput> 等 |
@Bean
public FreeMarkerConfigurer freemarkerConfig() {
FreeMarkerConfigurer c = new FreeMarkerConfigurer();
// ...
Map<String, Object> vars = new HashMap<>();
vars.put("appName", "MyApp");
vars.put("formatMoney", (TemplateMethodModelEx) args -> {
Number n = (Number) args.get(0);
return NumberFormat.getCurrencyInstance(Locale.TAIWAN).format(n);
});
c.setFreemarkerVariables(vars);
return c;
}
模板中直接使用:${appName}、${formatMoney(1234.5)}。
Spring MVC + FreeMarker 是經典且穩定的視圖層組合。掌握 **Configurer → ViewResolver → Controller → Template** 四層配置鏈路,並善用「開發熱更、生產緩存」、「共享變數/宏/佈局」等進階特性,即可構建高效可維護的渲染層。