适用场景:内容管理系统(CMS)、电商详情页、博客生成、SEO 优化页面
目标:将动态数据 + 模板 → 生成 .html 静态文件,提升访问速度、降低服务器压力


一、核心概念

1. 什么是静态页面生成?

模板(.ftl + 数据模型(Java 对象) → 通过 FreeMarker 渲染 → 输出为 .html 文件,部署到 Nginx、CDN 或静态服务器,用户直接访问 HTML,无需后端参与。

2. 为什么使用 FreeMarker 做静态化?

优势 说明
✅ 模板强大 支持逻辑、循环、宏、包含等
✅ 性能高 渲染快,适合批量生成
✅ 易集成 Java 生态,与 Spring、MyBatis 等无缝集成
✅ 可维护 模板与数据分离,便于前端协作
✅ 支持动态数据 可从数据库、API 获取数据再生成

3. 典型应用场景

  • 新闻网站:每发布一篇新闻,生成 news-123.html
  • 电商平台:商品详情页生成 product-456.html
  • 博客系统:文章发布后生成静态页
  • SEO 优化:静态页更利于搜索引擎抓取

二、操作步骤(非常详细)

步骤 1:添加 Maven 依赖

<dependencies>
    <!-- FreeMarker 核心 -->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.32</version>
    </dependency>

    <!-- 可选:Spring 集成(如使用 Spring) -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.3.31</version>
    </dependency>

    <!-- 可选:日志 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.11</version>
    </dependency>
</dependencies>

步骤 2:准备 FreeMarker 配置

import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.Version;

public class StaticPageGenerator {

    private Configuration cfg;

    public StaticPageGenerator() throws IOException {
        cfg = new Configuration(Version.VERSION_2_3_32);

        // 设置模板目录(classpath 或文件系统)
        // 方式一:从 classpath 加载模板
        cfg.setClassForTemplateLoading(StaticPageGenerator.class, "/templates");

        // 方式二:从文件系统加载
        // cfg.setDirectoryForTemplateLoading(new File("/path/to/templates"));

        // 设置默认编码
        cfg.setDefaultEncoding("UTF-8");

        // 异常处理:生产环境使用 RETHROW
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

        // 关闭模板缓存(开发期),生产批量生成可开启
        cfg.setTemplateUpdateDelayMilliseconds(Long.MAX_VALUE); // 永不检查更新

        // 日志设置
        cfg.setLogTemplateExceptions(false); // 避免敏感信息泄露
        cfg.setWrapUncheckedExceptions(true);
    }
}

步骤 3:准备数据模型(Java 对象)

public class Article {
    private Long id;
    private String title;
    private String content;
    private String author;
    private Date publishTime;
    private List<Comment> comments;

    // 构造函数、getter/setter 省略
}

public class Comment {
    private String nickname;
    private String text;
    private Date createTime;
    // getter/setter
}

步骤 4:创建 FreeMarker 模板(/resources/templates/article.ftl

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>${article.title} - 博客</title>
    <meta name="description" content="${article.content?substring(0, 100)}...">
</head>
<body>
    <header>
        <h1>${article.title}</h1>
        <p>作者:${article.author} | 发布时间:${article.publishTime?datetime}</p>
    </header>

    <article>
        ${article.content?no_esc} <!-- 内容可能含 HTML -->
    </article>

    <section>
        <h3>评论(${article.comments?size} 条)</h3>
        <#if article.comments??>
            <ul>
            <#list article.comments as comment>
                <li>
                    <strong>${comment.nickname}</strong>
                    <small>${comment.createTime?date}</small>
                    <p>${comment.text}</p>
                </li>
            </#list>
            </ul>
        <#else>
            <p>暂无评论。</p>
        </#if>
    </section>

    <footer>
        <p>&copy; 2025 我的博客. All rights reserved.</p>
    </footer>
</body>
</html>

步骤 5:编写静态页生成逻辑

import freemarker.template.Template;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public void generateArticlePage(Article article, String outputDir) 
        throws IOException, TemplateException {

    // 1. 获取模板
    Template template = cfg.getTemplate("article.ftl"); // 对应 /templates/article.ftl

    // 2. 构建数据模型
    Map<String, Object> dataModel = new HashMap<>();
    dataModel.put("article", article);

    // 3. 创建输出目录
    Path outputDirPath = Paths.get(outputDir);
    Files.createDirectories(outputDirPath);

    // 4. 定义输出文件路径
    String fileName = "article-" + article.getId() + ".html";
    Path outputPath = outputDirPath.resolve(fileName);

    // 5. 渲染并写入文件
    try (PrintWriter writer = new PrintWriter(new FileWriter(outputPath.toFile()))) {
        template.process(dataModel, writer);
        writer.flush();
    }

    System.out.println("✅ 静态页生成成功: " + outputPath);
}

步骤 6:调用生成方法(示例)

public static void main(String[] args) {
    try {
        StaticPageGenerator generator = new StaticPageGenerator();

        // 模拟从数据库加载文章
        Article article = new Article();
        article.setId(123L);
        article.setTitle("FreeMarker 静态化实战");
        article.setContent("<p>本文介绍如何使用 FreeMarker 生成静态页面...</p>");
        article.setAuthor("张三");
        article.setPublishTime(new Date());

        List<Comment> comments = Arrays.asList(
            new Comment("李四", "很有用!", new Date()),
            new Comment("王五", "已收藏", new Date())
        );
        article.setComments(comments);

        // 生成静态页
        generator.generateArticlePage(article, "/var/www/html/articles");

    } catch (Exception e) {
        e.printStackTrace();
    }
}

步骤 7:部署静态页

生成的文件位于:

/var/www/html/articles/article-123.html

可通过 Nginx 直接访问:

server {
    listen 80;
    root /var/www/html;
    index index.html;

    location /articles/ {
        alias /var/www/html/articles/;
    }
}

访问:http://yourdomain.com/articles/article-123.html


三、常见错误与解决方案

错误现象 原因 解决方案
TemplateNotFoundException 模板路径错误 检查 setClassForTemplateLoading 路径或文件是否存在
页面乱码 编码未统一 确保模板、defaultEncoding、输出文件均为 UTF-8
${...} 未替换 数据模型 key 错误 检查 dataModel.put("key", value) 与模板中 ${key} 一致
?no_esc 未生效 内容被转义 使用 ?no_esc?interpret(谨慎)
生成速度慢 大量文件未并行 使用多线程批量生成(见性能优化)
?datetime 格式错误 时区问题 设置 cfg.setLocale(Locale.CHINA); cfg.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

四、注意事项

  1. 模板路径classpath 路径以 / 开头,文件系统路径用 File
  2. 输出目录权限:确保 Java 进程有写权限。
  3. 文件覆盖:重复生成会覆盖原文件,注意备份。
  4. HTML 转义:用户内容用 ${text}(自动转义),可信 HTML 用 ${text?no_esc}
  5. 资源引用:CSS/JS 图片路径使用相对路径或 CDN 绝对路径。
  6. SEO 友好:在模板中添加 meta、title、schema 等。

五、使用技巧

1. 批量生成(多线程)

List<Article> articles = articleService.getAllPublished();
ExecutorService executor = Executors.newFixedThreadPool(10);

articles.forEach(article -> 
    executor.submit(() -> {
        try {
            generateArticlePage(article, outputDir);
        } catch (Exception e) {
            log.error("生成文章失败: " + article.getId(), e);
        }
    })
);

executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);

2. 模板热更新(开发期)

// 开发期:设置较短的更新延迟
cfg.setTemplateUpdateDelayMilliseconds(1000); // 每秒检查一次

3. 使用 #include 复用头部/尾部

<!-- header.ftl -->
<!DOCTYPE html>
<html>
<head><title>${title}</title></head>
<body>

<!-- article.ftl -->
<#include "header.ftl">
<h1>${article.title}</h1>
...
<#include "footer.ftl">

4. 生成目录页(列表页)

public void generateIndexPage(List<Article> articles, String outputDir) 
        throws IOException, TemplateException {
    
    Template template = cfg.getTemplate("index.ftl");
    Map<String, Object> dataModel = new HashMap<>();
    dataModel.put("articles", articles);
    
    try (PrintWriter writer = new PrintWriter(new FileWriter(Paths.get(outputDir, "index.html").toFile()))) {
        template.process(dataModel, writer);
    }
}

5. 自动发布到 CDN(可选)

生成后调用 API 上传到阿里云 OSS、腾讯云 COS 或 CDN 刷新。


六、最佳实践

实践 说明
✅ 模板与代码分离 放在 resources/templates/
✅ 数据模型扁平化 避免深层嵌套 getter
✅ 使用 ! 处理 null ${user.name!'游客'}
✅ 输出路径规范化 使用 Paths.get()
✅ 错误日志记录 捕获 TemplateException 并记录
✅ 支持增量生成 只生成更新的文章
✅ 生成前备份旧页(可选) 防止错误覆盖
✅ 使用构建工具集成 Maven/Gradle 插件生成

七、性能优化

优化项 说明
✅ 复用 Configuration 全局单例,避免重复解析
✅ 开启模板缓存 setTemplateUpdateDelayMilliseconds(Long.MAX_VALUE)
✅ 多线程批量生成 提升吞吐量
✅ 压缩 HTML 输出(可选) 减小文件体积
✅ 使用 NIO 写入 Files.newBufferedWriter
✅ 避免在模板中计算 预计算数据模型
✅ CDN 部署 提升全球访问速度

八、完整流程图

[触发生成] → [加载数据] → [获取模板] → [构建模型] → [渲染 HTML] → [写入文件] → [部署 CDN]
     ↑             ↑             ↑             ↑
  (发布文章)   (数据库/Service) (ftl 文件)  (Map<String, Object>)

九、总结:静态化检查清单 ✅

项目 是否完成
添加 FreeMarker 依赖
配置 Configuration
准备模板 .ftl
构建数据模型
调用 template.process() 写入文件
处理异常与日志
设置正确编码(UTF-8)
批量生成使用多线程
部署到 Web 服务器或 CDN

一句话总结
FreeMarker 静态化的核心是 “模板 + 数据 → HTML 文件”,通过 Java 程序批量生成,实现高性能、高可用的静态网站或页面。