Email

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

This section describes how to send email with the Spring Framework. .Library dependencies

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

The following JAR needs to be on the classpath of your application in order to use the Spring Framework’s email support:

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

This library is freely available on the web — for example, in Maven Central as com.sun.mail:jakarta.mail. Please make sure to use the latest 2.x version (which uses the jakarta.mail package namespace) rather than Jakarta Mail 1.6.x (which uses the javax.mail package namespace).

Spring Framework 提供了一个有用的实用程序库,用于发送电子邮件,该库可屏蔽底层邮件系统的具体信息,并负责代表客户端进行低级别资源处理。

The Spring Framework provides a helpful utility library for sending email that shields you from the specifics of the underlying mailing system and is responsible for low-level resource handling on behalf of the client.

org.springframework.mail 包是 Spring Framework 电子邮件支持的根级别包。用于发送电子邮件的中心接口是 MailSender`接口。一个封装简单邮件属性的简单值对象(例如 `fromto(还有许多其他属性))是 SimpleMailMessage 类。该包还包含了一系列经过检查的异常,这些异常在较底层的邮件系统异常之上提供了更高级别的抽象,而根异常是 MailException. 有关丰富的邮件异常层次结构的更多信息,请参见 javadoc.

The org.springframework.mail package is the root level package for the Spring Framework’s email support. The central interface for sending emails is the MailSender interface. A simple value object that encapsulates the properties of a simple mail such as from and to (plus many others) is the SimpleMailMessage class. This package also contains a hierarchy of checked exceptions that provide a higher level of abstraction over the lower level mail system exceptions, with the root exception being MailException. See the javadoc for more information on the rich mail exception hierarchy.

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

The org.springframework.mail.javamail.JavaMailSender interface adds specialized JavaMail features, such as MIME message support to the MailSender interface (from which it inherits). JavaMailSender also provides a callback interface called org.springframework.mail.javamail.MimeMessagePreparator for preparing a MimeMessage.

Usage

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

Assume that we have a business interface called OrderManager, as the following example shows:

  • Java

  • Kotlin

public interface OrderManager {

	void placeOrder(Order order);
}
interface OrderManager {

	fun placeOrder(order: Order)
}

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

Further assume that we have a requirement stating that an email message with an order number needs to be generated and sent to a customer who placed the relevant order.

Basic MailSender and SimpleMailMessage Usage

下面的示例展示了如何使用 MailSenderSimpleMailMessage 在有人下单时发送电子邮件:

The following example shows how to use MailSender and SimpleMailMessage to send an email when someone places an order:

  • Java

  • Kotlin

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;
	}

	@Override
	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());
		}
	}

}
class SimpleOrderManager : OrderManager {

	lateinit var mailSender: MailSender
	lateinit var templateMessage: SimpleMailMessage

	override fun 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

		val msg = SimpleMailMessage(this.templateMessage)
		msg.setTo(order.customer.emailAddress)
		msg.text = ("Dear " + order.customer.firstName
				+ order.customer.lastName
				+ ", thank you for placing order. Your order number is "
				+ order.orderNumber)
		try {
			mailSender.send(msg)
		} catch (ex: MailException) {
			// simply log it and go on...
			System.err.println(ex.message)
		}
	}
}

下面的示例展示了上述代码的 bean 定义:

The following example shows the bean definitions for the preceding code:

  • Java

  • Kotlin

  • Xml

@Bean
JavaMailSender mailSender() {
	JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
	mailSender.setHost("mail.mycompany.example");
	return mailSender;
}

@Bean // this is a template message that we can pre-load with default state
SimpleMailMessage templateMessage() {
	SimpleMailMessage message = new SimpleMailMessage();
	message.setFrom("customerservice@mycompany.example");
	message.setSubject("Your order");
	return message;
}

@Bean
SimpleOrderManager orderManager(JavaMailSender mailSender, SimpleMailMessage templateMessage) {
	SimpleOrderManager orderManager = new SimpleOrderManager();
	orderManager.setMailSender(mailSender);
	orderManager.setTemplateMessage(templateMessage);
	return orderManager;
}
@Bean
fun mailSender(): JavaMailSender {
	return JavaMailSenderImpl().apply {
		host = "mail.mycompany.example"
	}
}

@Bean // this is a template message that we can pre-load with default state
fun templateMessage() = SimpleMailMessage().apply {
	from = "customerservice@mycompany.example"
	subject = "Your order"
}


@Bean
fun orderManager(javaMailSender: JavaMailSender, simpleTemplateMessage: SimpleMailMessage) = SimpleOrderManager().apply {
	mailSender = javaMailSender
	templateMessage = simpleTemplateMessage
}
<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="customerservice@mycompany.example"/>
	<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>

Using JavaMailSender and MimeMessagePreparator

本部分描述了使用 MimeMessagePreparator 回调接口的 OrderManager 的另一种实现。在以下示例中,mailSender 属性的类型是 JavaMailSender,以便我们可以使用 JavaMail MimeMessage 类:

This section describes another implementation of OrderManager that uses the MimeMessagePreparator callback interface. In the following example, the mailSender property is of type JavaMailSender so that we are able to use the JavaMail MimeMessage class:

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("mail@mycompany.example"));
				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());
		}
	}

}

邮件代码是一种跨功能考虑,很可能是重构为 custom Spring AOP aspect 的候选代码,然后可以在 OrderManager 目标的适当连接点上运行。

The mail code is a crosscutting concern and could well be a candidate for refactoring into a custom Spring AOP aspect, which could then be run at appropriate joinpoints on the OrderManager target.

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

The Spring Framework’s mail support ships with the standard JavaMail implementation. See the relevant javadoc for more information.

Using the JavaMail MimeMessageHelper

在处理 JavaMail 消息时,一个特别有用的类是 org.springframework.mail.javamail.MimeMessageHelper,它可以避免您使用冗长的 JavaMail API。使用 MimeMessageHelper 很容易创建 MimeMessage,如下所示:

A class that comes in pretty handy when dealing with JavaMail messages is org.springframework.mail.javamail.MimeMessageHelper, which shields you from having to use the verbose JavaMail API. Using the MimeMessageHelper, it is pretty easy to create a MimeMessage, as the following example shows:

// 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("test@host.com");
helper.setText("Thank you for ordering!");

sender.send(message);

Sending Attachments and Inline Resources

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

Multipart email messages allow for both attachments and inline resources. Examples of inline resources include an image or a stylesheet that you want to use in your message but that you do not want displayed as an attachment.

Attachments

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

The following example shows you how to use the MimeMessageHelper to send an email with a single JPEG image attachment:

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("test@host.com");

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);

Inline Resources

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

The following example shows you how to use the MimeMessageHelper to send an email with an inline image:

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("test@host.com");

// 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 将内联资源添加到 MimeMessage(在上面的示例中为 identifier1234)。添加文本和资源的顺序非常重要。确保首先添加文本,然后添加资源。如果您反过来做,它将不起作用。

Inline resources are added to the MimeMessage by using the specified Content-ID (identifier1234 in the above example). The order in which you add the text and the resource are very important. Be sure to first add the text and then the resources. If you are doing it the other way around, it does not work.

Creating Email Content by Using a Templating Library

上一部分展示的示例中的代码使用诸如 message.setText(..) 这样的方法调用显式创建电子邮件消息内容。对于简单的情况来说,这种方法不错,并且这些示例的目的是向您展示 API 的基本知识,因此这样做并无问题。

The code in the examples shown in the previous sections explicitly created the content of the email message, by using methods calls such as message.setText(..). This is fine for simple cases, and it is okay in the context of the aforementioned examples, where the intent was to show you the very basics of the API.

然而,对于典型的企业应用程序,由于以下原因,开发者通常不会使用上述方法创建电子邮件消息内容:

In your typical enterprise application, though, developers often do not create the content of email messages by using the previously shown approach for a number of reasons:

  • Creating HTML-based email content in Java code is tedious and error prone.

  • There is no clear separation between display logic and business logic.

  • Changing the display structure of the email content requires writing Java code, recompiling, redeploying, and so on.

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

Typically, the approach taken to address these issues is to use a template library (such as FreeMarker) to define the display structure of email content. This leaves your code tasked only with creating the data that is to be rendered in the email template and sending the email. It is definitely a best practice when the content of your email messages becomes even moderately complex, and, with the Spring Framework’s support classes for FreeMarker, it becomes quite easy to do.