Spring 中配置发送邮件服务:从简单邮件到使用 Velocity 或 Thymeleaf 模板的邮件

3,751 阅读3分钟
原文链接: www.zifangsky.cn

前言:在当前大中型应用中邮件已经成为一种常用的通信方式。我们通常会配置Email来发送通知消息,比如:订单确认邮件、账户修改提醒邮件、密码重置确认邮件、交易扣款详情邮件等等。在使用了Spring的应用我们可以很容易地配置邮件发送服务,下面我将详细介绍其具体代码实现:

一 发送简单邮件

(1)引入关键jar包:

这里除了需要引入Spring的一些基本jar包之外,还需要引入的jar包是:

  • javax.mail-1.5.6.jar
  • spring-context-support-4.2.3.RELEASE.jar

(2)配置MailSender:

Spring Email抽象的核心是MailSender接口,同时Spring自带了一个MailSender接口的实现也就是JavaMailSenderImpl。它会使用JavaMail API来发送Email。因此我们首先需要将JavaMailSenderImpl装配为Spring应用上下文的一个bean,具体代码如下:

Java
package cn.zifangsky.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.MailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

@Configuration
public class EmailConfig {

  @Value("${mailserver.mxhichina.host}")
  private String host;
  
  @Value("${mailserver.mxhichina.port}")
  private String port;
  
  @Value("${mailserver.mxhichina.username}")
  private String username;
  
  @Value("${mailserver.mxhichina.password}")
  private String password;
  
  @Bean
  public MailSender mailSender(){
    JavaMailSenderImpl mailSenderImpl = new JavaMailSenderImpl();
    mailSenderImpl.setDefaultEncoding("UTF-8");
    mailSenderImpl.setHost(host);
    mailSenderImpl.setPort(Integer.valueOf(port));
    mailSenderImpl.setUsername(username);
    mailSenderImpl.setPassword(password);
    
    return mailSenderImpl;
  }

}

对应的属性文件email.properties是:

mailserver.mxhichina.host=smtp.mxhichina.com
mailserver.mxhichina.port=25
mailserver.mxhichina.port.ssl=465
mailserver.mxhichina.username=service@zifangsky.cn
mailserver.mxhichina.password=Qaz123456

注:这里的SMTP连接参数需要根据自己的实际情况进行修改

然后还需要在Spring的配置文件中配置属性文件的注入以及上面JavaConfig的自动扫描:

XHTML
 <bean id="configProperties"
    class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="locations">
      <list>
        <value>classpath:jdbc.properties</value>
        <value>classpath:email.properties</value>
      </list>
    </property>
  </bean>
  
  <bean id="propertyConfigurer"
    class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
      <property name="properties" ref="configProperties" />  
  </bean>

  <context:component-scan base-package="cn.zifangsky.manager.impl" />
  <context:component-scan base-package="cn.zifangsky.config" />

(3)配置邮件发送服务:

i)定义接口EmailSendManager.java:

Java
package cn.zifangsky.manager;

import javax.mail.MessagingException;

import cn.zifangsky.model.SimpleEmail;

public interface EmailSendManager {
  
  /**
   * 发送简单邮件
   * @param simpleEmail 简单邮件详情
   * @throws MessagingException 
   */
  public void sendEmail(SimpleEmail simpleEmail) throws MessagingException;
  
}

对应的实体类SimpleEmail是:

Java
package cn.zifangsky.model;

import java.io.File;
import java.util.Map;
import java.util.Set;

public class SimpleEmail {

  private Set<String> toSet;  //收件人
  private String subject;  //主题
  private String content;  //正文
  private boolean isHtml;  //正文是否是HTML
  private Map<String, File> attachments;  //附件路径
  private boolean isAttachment;  //是否有附件

  public Set<String> getToSet() {
    return toSet;
  }

  public void setToSet(Set<String> toSet) {
    this.toSet = toSet;
  }

  public String getSubject() {
    return subject;
  }

  public void setSubject(String subject) {
    this.subject = subject;
  }

  public String getContent() {
    return content;
  }

  public void setContent(String content) {
    this.content = content;
  }

  public boolean isHtml() {
    return isHtml;
  }

  public void setHtml(boolean isHtml) {
    this.isHtml = isHtml;
  }

  public Map<String, File> getAttachments() {
    return attachments;
  }

  public void setAttachments(Map<String, File> attachments) {
    this.attachments = attachments;
  }

  public boolean isAttachment() {
    return isAttachment;
  }

  public void setAttachment(boolean isAttachment) {
    this.isAttachment = isAttachment;
  }

}

ii)接口实现类——具体的邮件发送服务EmailSendManagerImpl.java:

Java
package cn.zifangsky.manager.impl;

import java.io.File;
import java.util.Map;
import java.util.Set;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;

import cn.zifangsky.manager.EmailSendManager;
import cn.zifangsky.model.SimpleEmail;

@Component("simpleEmailSendManagerImpl")
public class EmailSendManagerImpl implements EmailSendManager {
  
  @Value("${mailserver.mxhichina.username}")
  private String from;  //发送者
  
  @Autowired
  private JavaMailSender mailSender;
  
  @Override
  public void sendEmail(SimpleEmail simpleEmail) throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, simpleEmail.isAttachment());
    
    /**
     * 添加发送者
     */
    helper.setFrom(from);
    
    Set<String> toSet =simpleEmail.getToSet();
    /**
     * 添加接收者
     */
    helper.setTo(toSet.toArray(new String[toSet.size()]));
    
    /**
     * 添加主题
     */
    helper.setSubject(simpleEmail.getSubject());
    /**
     * 添加正文
     */
    helper.setText(simpleEmail.getContent(),simpleEmail.isHtml());
    
    /**
     * 添加附件
     */
    if(simpleEmail.isAttachment()){
      Map<String, File> attachments = simpleEmail.getAttachments();
      
      if(attachments != null){
        for(Map.Entry<String, File> attach : attachments.entrySet()){
          helper.addAttachment(attach.getKey(), attach.getValue());
        }
        
      }

    }
    
    mailSender.send(message);  //发送
  }

}

从上面的代码可以看出在Spring中配置邮件发送服务是比较简单的,只需要指定相应的发送者、接收者、主题、正文、附件等信息即可

(4)测试:

i)发送简单邮件:

Java
package cn.zifangsky.test.email;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;
import javax.mail.MessagingException;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.zifangsky.manager.EmailSendManager;
import cn.zifangsky.model.SimpleEmail;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/context/context.xml", "classpath:/context/springmvc-servlet.xml" })
public class TestSendEmail {

  @Resource(name = "simpleEmailSendManagerImpl")
  private EmailSendManager emailSendManager;

  /**
   * 发送简单邮件
   * @throws MessagingException
   */
  @Test
  public void sendSimpleEmail() throws MessagingException {
    SimpleEmail simpleEmail = new SimpleEmail();
    simpleEmail.setSubject("测试在Spring中发送邮件");

    Set<String> receivers = new HashSet<>();
    receivers.add("admin@zifangsky.cn");
    simpleEmail.setToSet(receivers);

    simpleEmail.setHtml(false);
    simpleEmail.setContent("Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、"
        + "事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。");

    simpleEmail.setAttachment(false);

    emailSendManager.sendEmail(simpleEmail);
  }
}

最后收到的邮件如下:

ii)测试发送带附件的邮件:

在上面的单元测试中添加一个新的测试方法:

Java
 /**
   * 发送带附件的邮件
   * @throws MessagingException
   */
  @Test
  public void sendEmailWithAttachment() throws MessagingException {
    SimpleEmail simpleEmail = new SimpleEmail();
    simpleEmail.setSubject("测试在Spring中发送带有附件的邮件");

    Set<String> receivers = new HashSet<>();
    receivers.add("admin@zifangsky.cn");
    simpleEmail.setToSet(receivers);

    simpleEmail.setHtml(false);
    simpleEmail.setContent(
        "Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 "
        + "这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 "
        + "这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 "
        + "对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。"
        + "Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消费。");

    simpleEmail.setAttachment(true);
    
    Map<String, File> attachments = new HashMap<>();  //附件集合
    /**
     * web项目中使用request.getSession().getServletContext().getRealPath("/uploads")获取路径
     */
    File sockjs = new File("C:/Users/Administrator/Desktop/sockjs.min.js");
    attachments.put(sockjs.getName(), sockjs);
    
    File stomp = new File("C:/Users/Administrator/Desktop/stomp.min.js");
    attachments.put(stomp.getName(), stomp);
    
    File jquery = new File("C:/Users/Administrator/Desktop/jquery-2.0.3.min.js");
    attachments.put(jquery.getName(), jquery);

    simpleEmail.setAttachments(attachments);
    
    emailSendManager.sendEmail(simpleEmail);
  }

最后收到的邮件如下:

iii)测试发送HTML格式的邮件:

同样在上面的单元测试中添加一个新的测试方法:

Java
 /**
   * 发送HTML格式的邮件
   * @throws MessagingException
   */
  @Test
  public void sendHTMLEmail() throws MessagingException {
    SimpleEmail simpleEmail = new SimpleEmail();
    simpleEmail.setSubject("测试在Spring中发送带HTML格式的邮件");

    Set<String> receivers = new HashSet<>();
    receivers.add("admin@zifangsky.cn");
    simpleEmail.setToSet(receivers);

    simpleEmail.setHtml(true);
    simpleEmail.setContent("<html><head><meta http-equiv=\"Content-Type\" "
        + "content=\"text/html; charset=UTF-8\"></head>"
        + "<body><div align=\"center\" style=\"color:#F00\">"
        + "<h2>测试在Spring中发送带HTML格式的邮件</h2></div></body></html>");

    simpleEmail.setAttachment(false);

    emailSendManager.sendEmail(simpleEmail);
  }

从上面代码可以得知发送HTML格式的邮件无非是正文是HTML格式,其他地方的设置跟上面的类似

最后收到的邮件如下:

二 发送Velocity模板的邮件

在上面我花了一定篇幅介绍了如何发送简单邮件,包括从最简单的没有样式的邮件再到HTML格式的邮件。但是在实际开发中我们通常需要发送样式比较丰富的邮件,如果像上面那样发送HTML格式的邮件的话不仅正文内容设置很繁琐,而且也不方便随时修改。因此这时就可以考虑将邮件正文定义成模板的形式,每次发送邮件只需要提前往模板中注入数据并发送即可

下面我将介绍如何发送Velocity模板的邮件:

(1)引入Velocity相关jar包:

  • velocity-1.7.jar

(2)配置VelocityEngineFactoryBean:

为了能够在发送邮件时使用Velocity模板,我们需要将VelocityEngine装配到应用中,Spring提供了一个名为VelocityEngineFactoryBean的工厂Bean,它能够在Spring应用上下文的很便利地生成VelocityEngine。因此在最上面的EmailConfig.java文件中添加以下代码即可:

Java
  @Bean
  public VelocityEngineFactoryBean velocityEngine(){
    VelocityEngineFactoryBean velocityEngine = new VelocityEngineFactoryBean();
    
    Properties properties = new Properties();
    properties.setProperty("resource.loader", "classpath");
    properties.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
    
    velocityEngine.setVelocityProperties(properties);
    
    return velocityEngine;
  }

(3)在EmailSendManager.java接口中添加一个新方法:

Java
 /**
   * 使用Velocity模板发送邮件
   * @param simpleEmail 简单邮件详情
   * @param model 模板参数
   * @param templateLocation 模板所在路径
   * @throws MessagingException
   */
  public void sendVelocityEmail(SimpleEmail simpleEmail,Map<String, Object> model,String templateLocation) throws MessagingException;

(4)对应修改EmailSendManagerImpl.java:

i)注入VelocityEngine:

Java
  @Autowired
  private VelocityEngine velocityEngine;

ii)补全具体的邮件发送逻辑:

Java
  @Override
  public void sendVelocityEmail(SimpleEmail simpleEmail, Map<String, Object> model, String templateLocation)
      throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, simpleEmail.isAttachment());
    
    /**
     * 添加发送者
     */
    helper.setFrom(from);
    
    Set<String> toSet =simpleEmail.getToSet();
    /**
     * 添加接收者
     */
    helper.setTo(toSet.toArray(new String[toSet.size()]));
    
    /**
     * 添加主题
     */
    helper.setSubject(simpleEmail.getSubject());
    /**
     * 添加正文
     */
    String emailContent = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, templateLocation, "UTF-8", model);
    helper.setText(emailContent,true);
    
    /**
     * 添加附件
     */
    if(simpleEmail.isAttachment()){
      Map<String, File> attachments = simpleEmail.getAttachments();
      
      if(attachments != null){
        for(Map.Entry<String, File> attach : attachments.entrySet()){
          helper.addAttachment(attach.getKey(), attach.getValue());
        }
      }
    }
    mailSender.send(message);  //发送
  }

这里需要注意的是,在第25行设置邮件正文的时候使用了Spring提供的VelocityEngineUtils将Velocity模板与模型数据自动合并成了String字符串,接着再将这个字符串设置成了邮件的正文

(5)测试:

在上面的单元测试中添加这样的一个测试方法:

Java
  /**
   * 发送Velocity模板的邮件
   * @throws MessagingException
   */
  @Test
  public void sendVelocityEmail() throws MessagingException {
    SimpleEmail simpleEmail = new SimpleEmail();
    simpleEmail.setSubject("测试在Spring中发送使用了Velocity模板的邮件");

    Set<String> receivers = new HashSet<>();
    receivers.add("admin@zifangsky.cn");
    simpleEmail.setToSet(receivers);
    
    Map<String, Object> params = new HashMap<>();
    params.put("title", "This's title");
    params.put("hello", "测试在Spring中发送使用了Velocity模板的邮件");

    simpleEmail.setAttachment(false);

    emailSendManager.sendVelocityEmail(simpleEmail, params, "emailTemplate.vm");
  }

注:这里用到的emailTemplate.vm模板的内容是:

XHTML
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>${title}</title>
</head>
<body>
  <div align="center" style="color:#22B14C">
    <h2>${hello}</h2>
    <img src="https://www.zifangsky.cn/wp-content/uploads/2016/12/20161217223656897.png" alt="测试正文包含图片"/>
  </div>
</body>
</html>

关于Velocity模板的语法请自行参考其他文档,我这里就不多做解释了

运行这个测试方法之后,最后收到的邮件如下:

三 发送Thymeleaf模板的邮件

在使用模板发送邮件的时候,我们除了可以使用Velocity模板还可以使用其他的一些模板引擎,比如:Thymeleaf模板

Thymeleaf是一种很有吸引力的HTML模板引擎,与JSP和Velocity不同,Thymeleaf模板不包括任何特殊的标签库和特有的标签,因此在设计和使用Thymeleaf模板时也将更变得更加方便

在Spring中想要发送使用了Thymeleaf模板的邮件,实际上我们所需要做的工作跟发送Velocity模板的邮件是类似的,具体来说就是下面这些步骤:

(1)引入Thymeleaf相关jar包:

  • thymeleaf-3.0.3.RELEASE.jar
  • thymeleaf-spring4-3.0.3.RELEASE.jar

(2)配置SpringTemplateEngine:

这里需要设置从类路径加载Thymeleaf模板。我这里重新新建了一个JavaConfig配置文件:

Java
package cn.zifangsky.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

/**
 * 从类路径加载Thymeleaf模板
 * @author zifangsky
 *
 */
@Configuration
public class TemplateConfig {

  @Bean
  public ClassLoaderTemplateResolver templateResolver(){
    ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
    templateResolver.setPrefix("/");
    templateResolver.setSuffix(".html");
    templateResolver.setTemplateMode("HTML5");
    templateResolver.setCharacterEncoding("UTF-8");
    templateResolver.setOrder(1);
    
    return templateResolver;
  }
  
  @Bean
  public SpringTemplateEngine templateEngine(ClassLoaderTemplateResolver templateResolver){
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
    
    return templateEngine;
  }
  
}

注:如果想要在Spring MVC中使用Thymeleaf模板渲染Web视图,那么就不应该配置ClassLoaderTemplateResolver而是ServletContextTemplateResolver ,具体可以参考我之前的这篇文章:www.zifangsky.cn/865.html

(3)在EmailSendManager.java接口中添加一个新方法:

Java
 /**
   * 使用Thymeleaf模板发送邮件
   * @param simpleEmail 简单邮件详情
   * @param model 模板参数
   * @param templateLocation 模板所在路径
   * @throws MessagingException
   */
  public void sendThymeleafEmail(SimpleEmail simpleEmail,Map<String, Object> model,String templateLocation) throws MessagingException;

(4)对应修改EmailSendManagerImpl.java:

i)注入SpringTemplateEngine:

Java
  @Autowired
  private SpringTemplateEngine thymeleafEngine;

ii)补全具体的邮件发送逻辑:

Java
  @Override
  public void sendThymeleafEmail(SimpleEmail simpleEmail, Map<String, Object> model, String templateLocation)
      throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, simpleEmail.isAttachment());
    
    /**
     * 添加发送者
     */
    helper.setFrom(from);
    
    Set<String> toSet =simpleEmail.getToSet();
    /**
     * 添加接收者
     */
    helper.setTo(toSet.toArray(new String[toSet.size()]));
    
    /**
     * 添加主题
     */
    helper.setSubject(simpleEmail.getSubject());
    /**
     * 添加正文
     */
    if(model != null){
      Context context = new Context();
      for(Map.Entry<String, Object> param : model.entrySet()){
        context.setVariable(param.getKey(), param.getValue());
      }
      
      String emailContent = thymeleafEngine.process("emailTemplate", context);
      helper.setText(emailContent,true);
    }
    
    /**
     * 添加附件
     */
    if(simpleEmail.isAttachment()){
      Map<String, File> attachments = simpleEmail.getAttachments();
      
      if(attachments != null){
        for(Map.Entry<String, File> attach : attachments.entrySet()){
          helper.addAttachment(attach.getKey(), attach.getValue());
        }
      }
    }
    mailSender.send(message);  //发送
    
  }

需要注意的是在设置正文时,这里的处理方式跟使用Velocity将模型数据填充到Map中类似。首先是创建了Thymeleaf的Context实例,接着将每个模型数据分别填充进去,最后通过调用Thymeleaf引擎的process( )方法将上下文中的模型数据合并到模板中并生成String字符串。当然,再之后肯定就是将这个字符串设置成邮件的正文啦,至此整个模板解析过程就完成了

(5)测试:

在上面的单元测试中添加这样的一个测试方法:

Java
  /**
   * 发送Thymeleaf模板的邮件
   * @throws MessagingException
   */
  @Test
  public void sendThymeleafEmail() throws MessagingException {
    SimpleEmail simpleEmail = new SimpleEmail();
    simpleEmail.setSubject("测试在Spring中发送使用了Thymeleaf模板的邮件");

    Set<String> receivers = new HashSet<>();
    receivers.add("admin@zifangsky.cn");
    simpleEmail.setToSet(receivers);
    
    Map<String, Object> params = new HashMap<>();
    params.put("title", "This's title");
    params.put("hello", "测试在Spring中发送使用了Thymeleaf模板的邮件");

    simpleEmail.setAttachment(false);

    emailSendManager.sendThymeleafEmail(simpleEmail, params, "emailTemplate.html");
  }

注:这里用到的emailTemplate.html模板的内容是:

XHTML
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title th:text="${title}">标题</title>
</head>
<body>
  <div align="center" style="color:#22B14C">
    <h2 th:utext="${hello}">hello</h2>
    <img src="https://www.zifangsky.cn/wp-content/uploads/2016/12/20161217223656897.png" alt="测试正文包含图片"/>
  </div>
</body>
</html>

关于Thymeleaf模板的语法请自行参考其他文档,我这里就不多做解释了

运行这个测试方法之后,最后收到的邮件如下:

好了,关于如何在Spring中配置邮件发送服务我就介绍这么多了,感兴趣的同学可以自行操作练习下,感谢大家观看!