1. 概述
在之前的 Spring 邮件教程 中,我们已经了解了如何使用 Spring 发送纯文本邮件。
但更进一步,我们还可以 ✅ 使用 Spring 支持的模板引擎来构建内容丰富、样式美观的 HTML 邮件,实现动态内容填充。
本文将带你实战使用两个最主流的模板引擎:Thymeleaf 和 FreeMarker,来生成并发送 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.ftl
、template-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)