电子邮件

本节介绍如何使用 Spring 框架发送电子邮件。

库依赖项

为了使用 Spring 框架的电子邮件支持,您的应用程序的类路径中需要以下 JAR 文件

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

Spring 框架提供了一个实用的邮件发送工具库,它可以屏蔽底层邮件系统的细节,并负责代表客户端处理低级资源。

org.springframework.mail 包是 Spring 框架邮件支持的根级别包。发送电子邮件的中心接口是 MailSender 接口。一个简单的值对象,封装了简单邮件的属性,例如 fromto(以及许多其他属性),是 SimpleMailMessage 类。此包还包含一个已检查异常的层次结构,它提供了对底层邮件系统异常的更高抽象级别,根异常是 MailException。有关丰富的邮件异常层次结构的更多信息,请参见 javadoc

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

用法

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

public interface OrderManager {

	void placeOrder(Order order);

}

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

基本 MailSenderSimpleMailMessage 用法

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

import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.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) {

		// Do the business calculations...

		// Call the collaborators to persist the order...

		// Create a thread-safe "copy" of the template message and customize it
		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) {
			// simply log it and go on...
			System.err.println(ex.getMessage());
		}
	}

}

以下示例显示了前面代码的 bean 定义。

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

<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.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 jakarta.mail.internet.MimeMessage;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.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) {
		// Do the business calculations...
		// Call the collaborators to persist the 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) {
			// simply log it and go on...
			System.err.println(ex.getMessage());
		}
	}

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

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

使用 JavaMail MimeMessageHelper

在处理 JavaMail 消息时,一个非常方便的类是 org.springframework.mail.javamail.MimeMessageHelper,它可以免除您使用冗长的 JavaMail API 的麻烦。使用 MimeMessageHelper,创建 MimeMessage 非常容易,如下面的示例所示

// of course you would use DI in any real-world cases
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();

// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected]");

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

// let's attach the infamous windows Sample file (this time copied to 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();

// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected]");

// use the true flag to indicate the text included is HTML
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);

// let's include the infamous windows Sample file (this time copied to 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)来定义电子邮件内容的显示结构。这样,您的代码只需要负责创建要在电子邮件模板中渲染的数据并发送电子邮件。当您的电子邮件内容变得稍微复杂时,这绝对是一种最佳实践,而且借助 Spring 框架对 FreeMarker 的支持类,这变得非常容易。