1. 概述

在之前的 Spring 邮件教程 中,我们已经了解了如何使用 Spring 发送纯文本邮件。

但更进一步,我们还可以 ✅ 使用 Spring 支持的模板引擎来构建内容丰富、样式美观的 HTML 邮件,实现动态内容填充。

本文将带你实战使用两个最主流的模板引擎:ThymeleafFreeMarker,来生成并发送 HTML 邮件。这类功能在用户注册确认、订单通知等场景中非常常见,属于后端开发的“踩坑高发区”——稍不注意,邮件就变成乱码或样式错乱。

我们不会重复造轮子,而是基于 Spring 的邮件支持,整合模板引擎,做到简洁高效。

2. Spring HTML 邮件基础

我们从之前的 EmailServiceImpl 类出发,先扩展一个支持 HTML 内容的方法:

private void sendHtmlMessage(String to, String subject, String htmlBody) throws MessagingException {
    MimeMessage message = emailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(htmlBody, true); // ✅ 第二个参数为 true 表示内容是 HTML
    emailSender.send(message);
}

关键点在于 helper.setText(htmlBody, true) 这一行。
⚠️ 第二个参数 true 明确告诉 Spring:这个邮件体是 HTML 格式,否则会被当作纯文本处理,标签都会原样显示。

接下来的重点,就是如何生成这个 htmlBody 字符串——我们将借助 Thymeleaf 和 FreeMarker 模板引擎来完成。

3. Thymeleaf 配置

我们创建一个 EmailConfiguration 配置类来集中管理模板引擎的 Bean。

3.1. 模板作为 Classpath 资源

最简单的方式是将模板文件打包进 JAR,与代码一起发布。这种方式便于版本控制和部署一致性。

模板文件放在 src/main/resources/mail-templates/ 目录下,我们使用 ClassLoaderTemplateResolver 来加载:

@Bean
public ITemplateResolver thymeleafTemplateResolver() {
    ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
    templateResolver.setPrefix("mail-templates/");
    templateResolver.setSuffix(".html");
    templateResolver.setTemplateMode("HTML");
    templateResolver.setCharacterEncoding("UTF-8");
    return templateResolver;
}
  • setPrefix:指定模板文件的相对路径前缀
  • setSuffix:自动添加的文件后缀
  • setTemplateMode("HTML"):明确模板类型为 HTML
  • setCharacterEncoding("UTF-8"):避免中文乱码

3.2. 模板放在外部目录

如果希望不重启服务就能修改邮件模板(比如运营临时调整文案),可以把模板放在外部文件系统。

我们通常通过 application.properties 配置路径:

spring.mail.templates.path=/opt/email-templates/

然后在配置类中注入:

@Value("${spring.mail.templates.path}")
private String mailTemplatesPath;

替换 thymeleafTemplateResolver 中的解析器为 FileTemplateResolver

@Bean
public ITemplateResolver thymeleafTemplateResolver() {
    FileTemplateResolver templateResolver = new FileTemplateResolver();
    templateResolver.setPrefix(mailTemplatesPath);
    templateResolver.setSuffix(".html");
    templateResolver.setTemplateMode("HTML");
    templateResolver.setCharacterEncoding("UTF-8");
    return templateResolver;
}

这样模板修改后,下次发送邮件时就会自动生效,无需重新打包。

3.3. 配置 Thymeleaf 引擎

最后一步,创建 SpringTemplateEngine 并注入前面定义的 ITemplateResolver

@Bean
public SpringTemplateEngine thymeleafTemplateEngine(ITemplateResolver templateResolver) {
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
    templateEngine.setTemplateEngineMessageSource(emailMessageSource()); // 支持国际化
    return templateEngine;
}

Spring 会自动将 ITemplateResolver 类型的 Bean 注入此方法,完成组装。

4. FreeMarker 配置

FreeMarker 的配置方式略有不同,我们同样在 EmailConfiguration 中配置。

4.1. 模板在 Classpath 中

使用 FreeMarkerConfigurer 来设置模板加载器:

@Bean 
public FreeMarkerConfigurer freemarkerClassLoaderConfig() {
    Configuration configuration = new Configuration(Configuration.VERSION_2_3_27);
    TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass(), "/mail-templates");
    configuration.setTemplateLoader(templateLoader);
    FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
    freeMarkerConfigurer.setConfiguration(configuration);
    return freeMarkerConfigurer; 
}
  • ClassTemplateLoader 从类路径加载模板
  • Configuration 是 FreeMarker 的核心配置对象

4.2. 模板在文件系统

只需替换 TemplateLoader 实现:

@Bean 
public FreeMarkerConfigurer freemarkerFileConfig() throws IOException {
    Configuration configuration = new Configuration(Configuration.VERSION_2_3_27);
    TemplateLoader templateLoader = new FileTemplateLoader(new File(mailTemplatesPath));
    configuration.setTemplateLoader(templateLoader);
    FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
    freeMarkerConfigurer.setConfiguration(configuration);
    return freeMarkerConfigurer; 
}

⚠️ 注意 FileTemplateLoader 构造时传入的是 File 对象。

5. 国际化支持(i18n)

Thymeleaf 的国际化

Thymeleaf 原生支持通过 MessageSource 实现多语言:

@Bean
public ResourceBundleMessageSource emailMessageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("mailMessages");
    return messageSource;
}

然后在 thymeleafTemplateEngine Bean 中设置:

templateEngine.setTemplateEngineMessageSource(emailMessageSource());

接着创建对应的语言包文件:

src/main/resources/mailMessages_en_US.properties
src/main/resources/mailMessages_zh_CN.properties

例如 mailMessages_en_US.properties 内容:

greetings=Hello {0}!
regards=Best regards
signature=Regards, {0}

FreeMarker 的国际化

FreeMarker 本身不提供类似 MessageSource 的机制,通常通过以下方式实现:

  • ❌ 不推荐:在 Java 层预处理文本,把翻译好的内容塞进 templateModel
  • ✅ 推荐:按语言创建多个模板文件,如 template-freemarker_en.ftltemplate-freemarker_zh.ftl,根据用户语言选择模板名

6. Thymeleaf 模板示例

模板文件 template-thymeleaf.html 内容如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <p th:text="#{greetings(${recipientName})}"></p>
    <p th:text="${text}"></p>
    <p th:text="#{regards}"></p>
    <p>
      <em th:text="#{signature(${senderName})}"></em> <br />
    </p>
  </body>
</html>

关键语法:

  • ${...}:插入变量值
  • #{...}:引用国际化消息

使用方式非常简单:

@Autowired
private SpringTemplateEngine thymeleafTemplateEngine;

@Override
public void sendMessageUsingThymeleafTemplate(
    String to, String subject, Map<String, Object> templateModel)
        throws MessagingException {
                
    Context thymeleafContext = new Context();
    thymeleafContext.setVariables(templateModel);
    String htmlBody = thymeleafTemplateEngine.process("template-thymeleaf.html", thymeleafContext);
    
    sendHtmlMessage(to, subject, htmlBody);
}
  • Context 对象承载模板变量
  • process() 方法返回渲染后的 HTML 字符串

7. FreeMarker 模板示例

FreeMarker 语法更简洁,但不支持内建国际化:

<!DOCTYPE html>
<html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
      <p>Hi ${recipientName}</p>
      <p>${text}</p>
      <p>Regards,</p>
      <p>
        <em>${senderName} at Baeldung</em> <br />
      </p>
    </body>
</html>

渲染代码:

@Autowired
private FreeMarkerConfigurer freemarkerConfigurer;

@Override
public void sendMessageUsingFreemarkerTemplate(
    String to, String subject, Map<String, Object> templateModel)
        throws IOException, TemplateException, MessagingException {
        
    Template freemarkerTemplate = freemarkerConfigurer.getConfiguration()
      .getTemplate("template-freemarker.ftl");
    String htmlBody = FreeMarkerTemplateUtils.processTemplateIntoString(freemarkerTemplate, templateModel);

    sendHtmlMessage(to, subject, htmlBody);
}
  • getTemplate() 加载模板
  • processTemplateIntoString() 执行渲染

8. 邮件中嵌入图片

HTML 邮件中常需要嵌入 Logo 或签名图。我们使用 MIME 的 CID(Content ID)机制实现。

修改 sendHtmlMessage 方法

private void sendHtmlMessage(String to, String subject, String htmlBody, Resource... inlineResources) 
    throws MessagingException {
    
    MimeMessage message = emailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); // ✅ 启用 multipart
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(htmlBody, true);

    // 添加内联资源
    if (inlineResources != null) {
        for (Resource resource : inlineResources) {
            helper.addInline(resource.getFilename(), resource);
        }
    }

    emailSender.send(message);
}

注入图片资源

@Value("classpath:/mail-logo.png")
private Resource logoResource;

模板中引用图片

在 Thymeleaf 或 FreeMarker 模板中使用 CID 引用:

<img src="cid:mail-logo.png" alt="Company Logo" />

调用时传入资源:

Map<String, Object> model = new HashMap<>();
// ... 设置 model

sendMessageUsingThymeleafTemplate(
    "user@example.com", 
    "Welcome!", 
    model, 
    logoResource // 作为内联资源传入
);

⚠️ 注意 addInline 的第一个参数必须与模板中 cid: 后的名称一致。

9. 总结

通过本文,你应该掌握了:

  • ✅ 使用 Thymeleaf 和 FreeMarker 生成 HTML 邮件内容
  • ✅ 模板的 classpath 和外部路径配置
  • ✅ 国际化支持方案
  • ✅ 邮件中嵌入图片的正确姿势

虽然两个模板引擎语法不同,但在 Spring 邮件场景下的集成方式大同小异。选择哪个更多取决于团队技术栈和模板复杂度。

Thymeleaf 语法更贴近 HTML,适合前端参与维护;FreeMarker 语法简洁,性能略优,适合纯后端场景。

示例完整代码已上传至 GitHub:https://github.com/baeldung/spring-tutorials(模块:spring-mvc-basics-2)


原始标题:Using ThymeLeaf and FreeMarker Emails Templates with Spring | Baeldung