电子邮件

本节介绍如何使用 TODAY Framework 发送电子邮件。

库依赖

为了使用 TODAY Framework 的电子邮件支持,您的应用程序的类路径中需要以下 JAR:

该库可以在网上免费获得——例如,在 Maven Central 中为 com.sun.mail:jakarta.mail。请确保使用最新的 2.x 版本(使用 jakarta.mail 包命名空间),而不是 Jakarta Mail 1.6.x(使用 javax.mail 包命名空间)。

TODAY Framework 提供了一个有用的实用程序库用于发送电子邮件,它使您免受底层邮件系统细节的影响,并负责代表客户端进行低级资源处理。

infra.mail 包是 Infra Framework 电子邮件支持的根级包。发送电子邮件的核心接口是 MailSender 接口。SimpleMailMessage 类是一个简单的值对象,封装了简单邮件的属性,如 fromto(以及许多其他属性)。该包还包含一个受检异常层次结构,它在低级邮件系统异常之上提供了更高级别的抽象,根异常为 MailException。有关丰富的邮件异常层次结构的更多信息,请参阅 javadoc

infra.mail.javamail.JavaMailSender 接口向 MailSender 接口(它继承自该接口)添加了专门的 JavaMail 功能,例如 MIME 消息支持。JavaMailSender 还提供了一个名为 infra.mail.javamail.MimeMessagePreparator 的回调接口,用于准备 MimeMessage

用法

假设我们有一个名为 OrderManager 的业务接口,如下例所示:

public interface OrderManager {

  void placeOrder(Order order);

}

进一步假设我们有一个要求,即需要生成并发送包含订单号的电子邮件消息给下订单的客户。

基本 MailSenderSimpleMailMessage 用法

以下示例显示了如何在有人下订单时使用 MailSenderSimpleMailMessage 发送电子邮件:

import infra.mail.MailException;
import infra.mail.MailSender;
import infra.mail.SimpleMailMessage;

public class SimpleOrderManager implements OrderManager {

  private MailSender mailSender;
  private SimpleMailMessage templateMessage;

  public void setMailSender(MailSender mailSender) {
    this.mailSender = mailSender;
  }

  public void setTemplateMessage(SimpleMailMessage templateMessage) {
    this.templateMessage = templateMessage;
  }

  public void placeOrder(Order order) {

    // 进行业务计算...

    // 调用协作者以持久化订单...

    // 创建模板消息的线程安全“副本”并对其进行自定义
    SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
    msg.setTo(order.getCustomer().getEmailAddress());
    msg.setText(
      "Dear " + order.getCustomer().getFirstName()
        + order.getCustomer().getLastName()
        + ", thank you for placing order. Your order number is "
        + order.getOrderNumber());
    try {
      this.mailSender.send(msg);
    }
    catch (MailException ex) {
      // 简单地记录并继续...
      System.err.println(ex.getMessage());
    }
  }

}

以下示例显示了上述代码的 Bean 定义:

<bean id="mailSender" class="infra.mail.javamail.JavaMailSenderImpl">
  <property name="host" value="mail.mycompany.example"/>
</bean>

<!-- 这是一个模板消息,我们可以使用默认状态预加载它 -->
<bean id="templateMessage" class="infra.mail.SimpleMailMessage">
  <property name="from" value="[email protected]"/>
  <property name="subject" value="Your order"/>
</bean>

<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
  <property name="mailSender" ref="mailSender"/>
  <property name="templateMessage" ref="templateMessage"/>
</bean>

使用 JavaMailSenderMimeMessagePreparator

本节介绍了 OrderManager 的另一种实现,它使用 MimeMessagePreparator 回调接口。在以下示例中,mailSender 属性的类型为 JavaMailSender,以便我们可以使用 JavaMail MimeMessage 类:

import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;

import infra.mail.MailException;
import infra.mail.javamail.JavaMailSender;
import infra.mail.javamail.MimeMessagePreparator;

public class SimpleOrderManager implements OrderManager {

  private JavaMailSender mailSender;

  public void setMailSender(JavaMailSender mailSender) {
    this.mailSender = mailSender;
  }

  public void placeOrder(final Order order) {
    // 进行业务计算...
    // 调用协作者以持久化订单...

    MimeMessagePreparator preparator = new MimeMessagePreparator() {
      public void prepare(MimeMessage mimeMessage) throws Exception {
        mimeMessage.setRecipient(Message.RecipientType.TO,
            new InternetAddress(order.getCustomer().getEmailAddress()));
        mimeMessage.setFrom(new InternetAddress("[email protected]"));
        mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
            order.getCustomer().getLastName() + ", thanks for your order. " +
            "Your order number is " + order.getOrderNumber() + ".");
      }
    };

    try {
      this.mailSender.send(preparator);
    }
    catch (MailException ex) {
      // 简单地记录并继续...
      System.err.println(ex.getMessage());
    }
  }

}
邮件代码是一个横切关注点,很可能适合重构为 自定义 Infra AOP 切面,然后可以在 OrderManager 目标上的适当连接点运行。

TODAY Framework 的邮件支持附带标准 JavaMail 实现。有关更多信息,请参阅相关 javadoc。

使用 JavaMail MimeMessageHelper

在处理 JavaMail 消息时,一个非常有用的类是 infra.mail.javamail.MimeMessageHelper,它可以使您免于使用冗长的 JavaMail API。使用 MimeMessageHelper,创建一个 MimeMessage 非常容易,如下例所示:

// 当然,您会在任何实际用例中使用 DI
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo("[email protected]");
helper.setText("Thank you for ordering!");

sender.send(message);

发送附件和内联资源

多部分电子邮件消息允许附件和内联资源。内联资源的示例包括您希望在消息中使用但不希望作为附件显示的图像或样式表。

附件

以下示例向您展示了如何使用 MimeMessageHelper 发送带有单个 JPEG 图像附件的电子邮件:

JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();

// 使用 true 标志表示您需要多部分消息
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected]");

helper.setText("Check out this image!");

// 让我们附上臭名昭著的 windows Sample 文件(这次复制到 c:/)
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addAttachment("CoolImage.jpg", file);

sender.send(message);

内联资源

以下示例向您展示了如何使用 MimeMessageHelper 发送带有内联图像的电子邮件:

JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();

// 使用 true 标志表示您需要多部分消息
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected]");

// 使用 true 标志表示包含的文本是 HTML
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);

// 让我们包含臭名昭著的 windows Sample 文件(这次复制到 c:/)
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addInline("identifier1234", res);

sender.send(message);
使用指定的 Content-ID(在上面的示例中为 identifier1234)将内联资源添加到 MimeMessage。添加文本和资源的顺序非常重要。请务必先添加文本,然后再添加资源。如果反过来做,则不起作用。

使用模板库创建电子邮件内容

前面几节中显示的示例中的代码使用诸如 message.setText(..) 之类的方法调用显式创建了电子邮件消息的内容。对于简单的情况,这很好,并且在上述示例的上下文中也是可以的,其目的是向您展示 API 的基础知识。

但是,在典型的企业应用程序中,开发人员通常不会使用前面显示的方法创建电子邮件消息的内容,原因如下:

  • 在 Java 代码中创建基于 HTML 的电子邮件内容既乏味又容易出错。

  • 显示逻辑和业务逻辑之间没有清晰的分离。

  • 更改电子邮件内容的显示结构需要编写 Java 代码、重新编译、重新部署等。

通常,解决这些问题的方法是使用模板库(如 FreeMarker)来定义电子邮件内容的显示结构。这使得您的代码仅负责创建要在电子邮件模板中呈现的数据并发送电子邮件。当您的电子邮件消息的内容变得稍微复杂时,这绝对是一个最佳实践,并且有了 TODAY Framework 对 FreeMarker 的支持类,这就变得很容易做到。