邮件

本节描述了如何使用Spring Framework发送电子邮件。

库依赖

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

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

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

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

org.springframework.mail.javamail.JavaMailSender接口添加了专门的JavaMail功能,例如对MailSender接口的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) {

		// 进行业务计算...

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

		// 创建模板消息的线程安全“副本”并自定义它
		SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
		msg.setTo(order.getCustomer().getEmailAddress());
		msg.setText(
			"亲爱的" + order.getCustomer().getFirstName()
				+ order.getCustomer().getLastName()
				+ ",感谢您的订单。您的订单号是"
				+ order.getOrderNumber());
		try {
			this.mailSender.send(msg);
		}
		catch (MailException ex) {
			// 简单记录并继续...
			System.err.println(ex.getMessage());
		}
	}

}

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

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

<!-- 这是一个我们可以预先加载默认状态的模板消息 -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
	<property name="from" value="[email protected]"/>
	<property name="subject" value="您的订单"/>
</bean>

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

使用JavaMailSenderMimeMessagePreparator

本节描述了另一种使用MimeMessagePreparator回调接口的OrderManager实现。在以下示例中,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) {
		// 进行业务计算...
		// 调用协作者来持久化订单...

		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("亲爱的" + order.getCustomer().getFirstName() + " " +
						order.getCustomer().getLastName() + ",感谢您的订单。" +
						"您的订单号是" + order.getOrderNumber() + ".");
			}
		};

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

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

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

使用JavaMail MimeMessageHelper

在处理JavaMail消息时非常方便的一个类是org.springframework.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("感谢您的订购!");

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("看看这张图片!");

// 让我们附加臭名昭著的Windows示例文件(这次复制到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示例文件(这次复制到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 Framework对FreeMarker的支持类,这变得非常容易。